Skip to content

feat(#267): adopt persist v17.4.0 resolve_scores — delete the Composer client-fold + two N+1s - #270

Open
emooreatx wants to merge 1 commit into
mainfrom
feat/267-resolve-scores
Open

feat(#267): adopt persist v17.4.0 resolve_scores — delete the Composer client-fold + two N+1s#270
emooreatx wants to merge 1 commit into
mainfrom
feat/267-resolve-scores

Conversation

@emooreatx

Copy link
Copy Markdown
Contributor

persist v17.4.0 shipped resolve_scores/list_scores (FSD-005 Appendix C) — the composed (subject, dimension, trust, time, state) → verdict fold now executes substrate-side as a composite op (the #329 pattern, CIRISPersist#455/#456 executor notes honored). This PR adopts both handles, deletes the Composer client-fold's substrate reads, and removes the two N+1s the 2026-07-14 demand survey flagged.

Pins

ciris-persist v17.3.0 → v17.5.0 and ciris-edge v13.0.9 → v13.1.0 in lockstep (edge v13.1.0 is the tag that re-pins persist v17.5.0; edge and server share persist types across the Engine/SqliteBackend seam, so the graph must unify on ONE persist). Applied to the root manifest (sqlite + postgres + dev-deps) and crates/ciris-lens-core. Verify stays v10.2.0 (matches edge v13.1.0's own pin).

Site 1 — src/compose_policy.rs::compose_for_key

Before: list_attestations_for(subject) fetched the WHOLE signed history; the fold (type==SCORES, trust set, staleness, group-by-(dim,subject), aggregation, licensure cap) ran client-side — plus an N+1: two has_effective_role calls per licensure:* attester in that history.

After: ONE resolve_scores(caller, filter, policy, trace=true) per the issue. The pin-once AttestationFilter carries {subject_key_id, dimension_exact, valid_at: now, lifecycle: Live, attester_filter: Explicit(pinned trust set)}; the policy is cc-4.4.2-signed-mean / cc-4.4.2-boolean-min from the dimension's CC 3.1 polarity column. Trust gating, staleness, CEG §6.1 retraction precedence, per-attester latest-wins, and the aggregation all run inside persist's .so; the verdict comes back as a ConfidenceBand + n's + the open trace, and is mapped into the existing public Composition return type.

N+1 removed: co-steward resolution now runs over the fold's head attesters only (bounded by contributor_count, never by history size) — and only for licensure:* dimensions.

Kept intact (per the issue's constraints):

  • the pure Composer::compose(&[Attestation]) — the CC 4.4 normative surface tests/compose_policy.rs drives adversarially — is untouched, as are all its tests;
  • TrustSet::pin_co_steward remains (consumer pins resolve first, substrate has_effective_role second);
  • compose_for_key still returns Composition (no callers existed in-tree; grep confirmed).

Composition-fidelity deltas (documented in the doc comment)

  • Signature gained dimension — persist's verdict is per (subject, dimension); the returned Composition carries exactly one Verdict, ALWAYS (an empty fold is now a visible Decision::Undetermined verdict rather than an absent one).
  • persist owns value and the band: Verdict::value is the fold's trace aggregate; InsufficientWitnessesUndetermined, otherwise the consumer threshold decides over the aggregate as before.
  • CC 4.4.1 weighting not yet substrate-side (Frickerian low-density, CC 3.4.7 self-track-record): contributions report weight = 1.0; persist's fold marks the server-tier column-resolver refinement as its own TODO.
  • Polarity coverage: persist executes signed-mean and boolean-min; Detector/Enumerated/PositiveOnly dimensions currently fold under the signed-mean default (flagged; Verdict::polarity still reports the CC 3.1 column).
  • No refusal ledger: gated/untrusted/expired rows are excluded server-side and never cross the wire (no verdict-differencing) — Composition::refusals now only carries the CC 3.4.5 re-check below.
  • CC 3.4.9 licensure cap kept client-side (a CCC MUST): single-source ⇒ reported confidences capped ≤ 0.5 + single_source_licensure = true; the persist-owned value/band are NOT re-capped.
  • CC 3.4.5 self-emission re-check kept (CC 3.4.7 "Both checks MUST agree"): a self-emitted capacity:* head that somehow passed the substrate gate is surfaced as a SelfEmission refusal and the verdict fails closed to Undetermined.
  • CC 3.1.9.3 sole-evidence screen (slashing:* on testimonial-only evidence_refs) needs the raw corpus; it runs at admission + in the pure compose tier only (flagged).
  • No CC 4.4.3.9 Policy D tie-break in the substrate fold (deterministic latest-asserted/lex-id instead).

Site 2 — src/graph_config.rs

Before: list_attestations_by(node) (every row, every type) filtered client-side to SCORES && dimension == config:v1, then per surviving row a second full list_attestations_by(node) walk + a revocations_for probe (config_key_revoked) — the O(N²) revocation re-scan N+1.

After: ONE cursor-paged list_scores("", {attesting_key_id: node, subject_key_id: node, attestation_type: scores, dimension_exact: config:v1, lifecycle: Live}, cursor, 512) seek over the V106 projection. Withdrawn/superseded/recanted rows are excluded server-side; config_key_revoked is deleted. The filter is built once and reused across pages (pin-once, Appendix C.4; #[non_exhaustive]-safe construction).

Wire behavior preserved: get/set semantics, version chaining, latest-wins fold, ConfigValue::Null tombstones, and revoke-reads-as-absent are unchanged (the per-key version fold is config semantics and deliberately stays client-side). One convention note: retraction targeting now speaks the CEG §6.1 canonical envelope member references_attestation_id (which persist's lifecycle fold honors) instead of config_key_revoked's ad-hoc attested_key_id/subject_key_ids matching — nothing in production ever emitted the ad-hoc shapes; tests/graph_config.rs::recant_row was moved to the canonical member and the recant-reads-as-absent pin still passes.

Gates

  • cargo build --release -j 1 — clean (16m, 0 warnings)
  • cargo test --release -j 1373 passed, 0 failed, 13 ignored across 41 test binaries (incl. the compose_policy, graph_config, and release_gates suites)
  • cargo clippy --all-targets -j 1 — no new warnings
  • cargo fmt --all — applied

The one release-gate that asserts the substrate pin (gate1_substrate_pins_at_target) was bumped to v17.5.0 / v13.1.0 alongside the manifest.

Closes #267

🤖 Generated with Claude Code

https://claude.ai/code/session_01MT2o477HcDTJJmY8GAystF

… the Composer client-fold + two N+1s

persist v17.5.0 (edge v13.1.0 lockstep) ships resolve_scores/list_scores
(FSD-005 Appendix C, CIRISPersist#455/#456) — the composed
(subject, dimension, trust, time, state) -> verdict fold now runs
substrate-side as a composite op (the #329 pattern). This retires the
fetch-whole-history-then-fold-in-Rust pattern at both demand-survey sites.

Site 1 — compose_policy.rs::compose_for_key:
  before: list_attestations_for(subject) (whole signed history) + an
    in-Rust fold + an N+1 has_effective_role pair per licensure attester
    IN THAT HISTORY.
  after:  ONE resolve_scores(caller, filter, policy, trace) per the issue.
    The pin-once AttestationFilter carries {subject, dimension_exact,
    valid_at: now, lifecycle: Live, attester_filter: Explicit(pinned)};
    trust gate + staleness + CEG §6.1 precedence + latest-wins +
    aggregation all run in persist's .so. Verdict (ConfidenceBand + n's +
    open trace) mapped into the existing Composition return type. The
    licensure N+1 collapses to co-steward resolution over the fold's HEAD
    attesters only (bounded by contributor_count).
  kept: the pure Composer::compose (CC 4.4 normative surface, adversarial
    tests untouched); TrustSet::pin_co_steward; CC 3.4.9 licensure cap +
    CC 3.4.5 self-emission re-check (client-side, per CC 3.4.7).
  fidelity deltas (doc'd on compose_for_key): signature gained `dimension`
    (verdict is per (subject,dimension), always one Verdict); persist owns
    value/band; CC 4.4.1 weighting + CC 4.4.3.9 Policy D + the
    Detector/Enumerated/PositiveOnly polarities not yet substrate-side
    (flagged); no refusal ledger (gated rows excluded server-side).

Site 2 — graph_config.rs:
  before: list_attestations_by(node) filtered client-side to SCORES &&
    config:v1, then a full list_attestations_by re-scan + revocations_for
    probe PER row (config_key_revoked) — the O(N^2) revocation N+1.
  after:  ONE cursor-paged list_scores({attester:node, subject:node,
    type:scores, dimension_exact: config:v1, lifecycle: Live}) seek;
    retracted rows excluded server-side. config_key_revoked deleted.
    Wire behavior (get/set, version chain, latest-wins, Null tombstones,
    revoke-reads-as-absent) preserved; recant targeting now speaks the
    CEG §6.1 canonical references_attestation_id (test updated).

Pins: ciris-persist v17.3.0->v17.5.0, ciris-edge v13.0.9->v13.1.0 (root +
crates/ciris-lens-core); release-gate TARGET_PERSIST/TARGET_EDGE bumped.

Closes #267

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MT2o477HcDTJJmY8GAystF
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@emooreatx

Copy link
Copy Markdown
Contributor Author

⚠️ Do not auto-merge — needs a human CC-conformance review first. This changes the composed-verdict path on a CC 4.4-gated surface, and the implementation flags real fidelity deltas that are NOT yet substrate-side: CC 4.4.1 weighting (contributions report weight=1.0), CC 4.4.3.9 Policy D tie-break, and the Detector/Enumerated/PositiveOnly polarities — all currently folded under persist's signed-mean rather than the CC-normative rules. The pure Composer::compose (adversarial suite) is preserved, so this is safe to hold behind review. It is intentionally OFF the 0.5.117 release path (mobile gate). Recommend: confirm each delta is an acceptable interim (or gate the new read behind a flag until persist carries the full CC 4.4 semantics) before merge.

@emooreatx

Copy link
Copy Markdown
Contributor Author

Heads-up for the CC review: persist v17.5.2 (#461) fixes list_attestations dimension_exact being a silent no-op in v17.5.0. This PR's graph_config path uses list_scores({dimension_exact: config:v1}) — on the v17.5.0 pin that filter may have returned unfiltered rows (over-matching). Re-validate this PR against v17.5.2 (which 0.5.118 pins) before merge; the dimension_exact behavior it relies on only became real in 17.5.2.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Adopt persist v17.4.0 resolve_scores — delete the Composer client-fold + the two N+1s

1 participant