refactor(capco,engine): PR 4b-D.2 β hot-path flip (Engine::project + JoinSemilattice::join)#527
Merged
Merged
Conversation
Type bridge for PR 4b-D hot-path flip. CapcoScheme::project returns CapcoMarking (wrapping CanonicalAttrs); the engine consumes ProjectedMarking through RuleContext::page_marking. This constructor projects the lattice-path output into the engine-facing shape. The constructor lives in marque-ism because ProjectedMarking is #[non_exhaustive] β its constructor MUST live in the type's home crate so cross-crate callers cannot bypass field-addition migrations (Constitution Principle VII). Both CapcoScheme::project (in marque-capco) and Engine::lint (in marque-engine) will route through this single source of truth. Field mapping is verbatim per-axis from CanonicalAttrs; scope is set to Scope::Page; provenance is the default (per-portion span attribution lands when the projection pipeline grows a contribution-tracking layer, out of scope for 4b-D). CAB-only fields on CanonicalAttrs (classified_by, derived_from, declass_exemption, token_spans) are intentionally absent per the type-level "page aggregate, not a CAB" contract. CanonicalAttrs::sar_markings is already Option<SarMarking> (singular) on staging, so the bridge is a direct field move β no cardinality debug_assert needed. No behavioral change in this commit β the bridge is unused until commit 3 wires CapcoScheme::project to drive through it. 9 unit tests cover empty round-trip, every supported classification variant (US / NATO / Joint), SAR singleton preservation, FGI marker preservation, dissem / non_ic_dissem / rel_to / sci_controls preservation, declassify_on preservation, and silent drop of CAB-only fields. PR 4b-D commit 1/5.
PR 4b-D.2 commit 2/5. CapcoMarking's JoinSemilattice::join impl delegates to join_via_lattice (the PR 4b-B per-axis lattice composer) instead of PageContext::add_portion + page_context_to_attrs. The lattice path is the post-PR-4b-B authoritative aggregation surface. The parity gate at crates/capco/tests/page_context_lattice_parity.rs already enforces byte-identity (or documented Β§-cited divergence) between the two paths across 74 fixtures. The flip exercises the same code join_via_lattice already runs; the parity-gate project_via_lattice helper hits this exact function indirectly through the fold and the helper continues to pass. No recursion concern: join_via_lattice's per-axis composition calls .join on SarSet / FgiSet (per-axis lattice types), never on CapcoMarking itself. MeetSemilattice keeps its narrow PageContext-free shape β widening it is independent work outside PR 4b-D.2 scope. Module-level Phase-B status note updated to reflect the post-PR-4b-D.2 state (the prior "STILL DELEGATES TO PageContext" language is replaced with the lattice-path-authoritative framing).
β¦writes
PR 4b-D.2 commit 3/5. Three changes land atomically:
1. CapcoScheme::project(Scope::Page|Document|Diff) now drives the
join_via_lattice path β closure operator β declarative
PageRewrite catalog. Pipeline ordering per
docs/plans/2026-05-01-lattice-design.md Β§4.7.4:
parse β join (lattice) β Cl_supp (closure)
β PageRewrites β render
The closure operator and PageRewrites are both monotone;
PageRewrites operate on the closure's fixed point.
2. Closure-rewrite-application sentinel (decisions.md D23): a
#[cfg(debug_assertions)]-gated debug_assert verifies the closure
operator did not mutate the per-portion CanonicalAttrs slice,
per lattice-design Β§3 (e.1) read-only-attrs invariant. Sibling
sentinel to the PageFinalization rule-dispatch sentinel in
engine.rs (PR #490). Ships in cargo test + unoptimized builds;
zero cost in --release.
3. apply_fact_add NOFORN supersession (decisions.md D22): when
FactAdd injects NOFORN into CAT_DISSEM, the Β§H.8 p145
supersession overlay applies at the injection site via
DissemSet::with_noforn_injected. Pre-fix the path appended Nf
to dissem_us without re-applying overlays, leaving {Nf,
Displayonly} or {Nf, Relido} in the bag β invalid per Β§H.8 p145.
Post-fix the injection is correct by construction, idempotent
under re-insertion, and works equally for closure-driven and
rule-driven FactAdd paths.
Authority (re-verified 2026-05-17 against crates/capco/docs/
CAPCO-2016.md):
- Β§H.8 p145 (NOFORN: "Cannot be used with REL TO, RELIDO, EYES
ONLY, or DISPLAY ONLY")
- Β§D.2 Table 3 rows 1-2 (NOFORN dominates FD&R controls)
- Β§H.8 p157 (EYES ONLY: NSA-only marking, parser preserves
DissemControl::Eyes through lint; supersession table covers it)
- Β§H.7 p127 + Β§G.2 Table 5 p40 (closure NATO row)
- Β§H.8 p163 + Β§D.2 Table 3 rows 25-27 (DISPLAY ONLY axis roll-up
via tmp_ctx.expected_display_only() β parity-gate helper
updated to match)
13 closure_hotpath integration tests at
crates/engine/tests/closure_hotpath.rs:
- 7 CLOSURE_NOFORN_* rows (SAR, AEA RD, UCNI, FGI, ORCON, RSEN,
non-IC LIMDIS) firing through the production page projection
- CLOSURE_REL_TO_USA_NATO row firing on bare NATO classification
- Idempotence: classified+ORCON and bare-NATO-at-closure-layer
- Monotonicity sanity on extending fact set
- apply_fact_add NOFORN supersession: strips DISPLAY ONLY at
injection time via DissemSet::with_noforn_injected
- apply_fact_add NOFORN idempotence under re-injection
DISPLAY ONLY axis wired into the lattice path's join output
mirrors page_context_to_attrs' existing semantic β scheme_equivalence's
project_banner_display_only_intersection_matches_pagecontext pins
the per-portion intersection roll-up that the new path now drives.
The parity-gate helper (project_via_page_context) is updated to
match so the 74-fixture byte-identity matrix continues to enforce
both paths producing the same output.
page_context_to_attrs survives behind #[allow(dead_code)] until
PR 4b-E retires the PageContext aggregator entirely; the function
is still referenced by parity-gate prose and unused by production
code.
Authorization: engine-crate touch under Constitution VII Β§IV
within-006 precedent (PR 4b-B Commit 2 / Β§7.B; PR 4b-C bugfix-class
deletions). This PR touches marque-capco only; the engine.rs flip
lands in commit 4.
PR 4b-D.2 commit 4/5. Engine::lint and dispatch_page_finalization now build the page-marking projection via `scheme.project(Scope::Page, ...)` instead of `PageContext::project()`. The new `project_page_marking` helper centralizes the per-portion CapcoMarking conversion and the `ProjectedMarking::from_canonical` bridge (commit 1) so both call sites share one source of truth. Authorization for engine-crate touch: Constitution VII Β§IV within-006 precedent established by PR 4b-B (PR 4b-B commit 2 / Β§7.B) and PR 4b-C (bugfix-class deletions in marque-ism). The flip is the single substantive engine-crate edit; PageContext stays alive as the transitional page-state accumulator and deletes in PR 4b-E alongside its supporting machinery. Pipeline change: pre-flip, `PageContext::project()` produced a ProjectedMarking via `expected_*` accessors that did NOT run closure or PageRewrites. Post-flip, the lattice + closure + PageRewrite pipeline drives the per-page roll-up, so banner-validation rules that read `ctx.page_marking` see the full Β§B.3 Table 2 closure cone (NOFORN implications, NATO REL TO injection) and every declarative PageRewrite (Pattern-B/C strip rows + the seven NOFORN-implication rows). The 74-fixture parity gate at `crates/capco/tests/page_context_lattice_parity.rs` enforces both paths agree on the documented divergence set. PageContext stays alive: add_portion calls and the per-portion candidate-loop fields are untouched. RuleContext continues to expose both ctx.page_context (the PageContext aggregator) and ctx.page_marking (now derived from scheme.project). PR 4b-E retires PageContext entirely; this PR's scope is only the projection driver. Sibling sentinel relocation: the doc-block at the PageFinalization-dispatch sentinel is updated to describe the scheme-side sentinel that landed in commit 3 (CapcoScheme::project's read-only-attrs assertion). Both sentinels fire under #[cfg(debug_assertions)] and together pin the Β§3 (e.1) read-only-attrs invariant across the engine's two consumer surfaces. Bench (lint_10kb, ubuntu-latest equivalent on dev WSL2 worktree): the closure + PageRewrite pipeline now runs once per portion-bounded page-marking cache miss (~20 times in the 10KB bench input). The new cost surfaces as a substantive bench regression that exceeds the +10% noise-band threshold; this is the expected cost of the flip and the user-approved bench-baseline-staleness pattern routes the routine refresh through PR 5+. Detailed measurement and mitigation discussion lives in the PR body.
β¦oc sync
PR 4b-D.2 commit 5/5. Three pieces of cleanup work landing
atomically:
1. Pattern D corpus coverage at
crates/engine/tests/lattice_corpus.rs (4 tests + 4 in
closure_hotpath.rs from commit 3 = 7 Pattern-D scenarios total,
each carrying its Β§-citation):
- closure_nato_rel_to_solely_nato (Β§H.7 p127 + Β§G.2 Table 5 p40
+ Β§H.7 pp123-125): solely-NATO page; closure injects USA/NATO
silently; classification preserved as Nato(_).
- closure_nato_rel_to_us_plus_nato (Β§H.7 pp123-125 + Β§H.7 p127):
US+NATO mixed; classification flattens to Us(_) via reciprocal
raise; closure does NOT inject (TOK_NATO_CLASS absent after
flatten); NATO survives on FGI axis for S007 to surface.
- closure_relido_unanimity_all_portions (Β§H.8 pp155-156): unanimous
RELIDO survives banner roll-up.
- closure_relido_unanimity_drops_on_disagreement (Β§H.8 pp155-156):
non-unanimous RELIDO dropped at roll-up.
The four NOFORN-implication scenarios (SAR / AEA RD / UCNI / FGI /
ORCON) live in closure_hotpath.rs from commit 3. Together the seven
tests cover the Pattern D fixture set the PR brief called for.
The brief's `input.json` + `expected.ndjson` file format was
considered but rejected: CanonicalAttrs does not derive
serde::Serialize and adding it would be an out-of-scope cross-crate
type-system change (Constitution VII). Inline-typed Rust tests
capture the same Pattern-D coverage with stronger type-checking
and zero risk of fixture-file format drift; the rationale lives in
the lattice_corpus.rs module doc-comment.
2. Parity-gate fixture retargets in
crates/capco/tests/page_context_lattice_parity.rs (PR 4b-D.2):
- `fouo_classified_pagecontext_and_lattice_both_keep_fouo_pending_pr_4b_d`
renamed to `fouo_classified_scheme_project_strips_fouo`. Now
asserts the three-way comparison: per-axis helpers keep FOUO;
`project_via_scheme` strips it through
`capco/classification-evicts-fouo` + `capco/fouo-evicted-by-classified`
PageRewrites (Β§H.8 p134).
- `aea_ucni_classified_pagecontext_and_lattice_both_keep_ucni_pending_pr_4b_d`
renamed to `aea_ucni_classified_scheme_project_strips_and_promotes_noforn`.
Asserts per-axis helpers keep UCNI; `project_via_scheme` strips
UCNI AND promotes NOFORN via
`capco/dod-ucni-evicted-by-classified` + `capco/dod-ucni-promotes-noforn-when-classified`
(Β§H.6 p116).
- The three active classification-axis divergences
(`pure_nato_lattice_vs_pagecontext_diverges`,
`joint_unanimous_two_portions`, `joint_single_portion_no_us`)
gain a third comparison column: `project_via_scheme` agrees
with the lattice path on all three (both go through
join_via_lattice), so the disagreement is now scheme/lattice
vs PageContext.
3. CAPCO-CONTEXT.md Β§3 sync: removed the stale
`relido_plus_nf_noforn_dominates_documented_divergence` row
(converged in staging pre-PR-4b-D.2); renamed the two
`_pending_pr_4b_d` rows; updated the active divergence count
from 4 (or 6) to 3 (NATO classification + 2 JOINT cases β
all classification-axis, all renderer-territory pending PR 5+);
documented the post-flip parity-gate direction inversion
(scheme/lattice agree; PageContext is the divergent side until
PR 4b-E retires it). Re-verified all Β§-citations against
`crates/capco/docs/CAPCO-2016.md` at authoring time per
Constitution VIII.
4. decisions.md: D22 (NOFORN-supersession at FactAdd injection
site) and D23 (closure-rewrite-application sentinel placement
inside CapcoScheme::project) recorded as PR 4b-D.2 decisions.
Parity gate count after retargets: still 74 #[test] fixtures, all
green. The three active divergences are now documented as
classification-axis-only renderer-territory cases pending PR 5+
(no longer "post-PR-4b-D-pending" β PR 4b-D.2 IS the flip; the
divergences are inherent to the JointSet / Nato-class variant
preservation choice and won't close until the renderer trait
surface lands).
PR 4b-D.2 commit 6/N. Implements the architect's R-1 mitigation referenced in the PR brief: skip the snapshot-and-fixpoint loop in `CapcoScheme::closure` when no catalog rule's trigger fires on the input marking. The closure is a guaranteed no-op in that case (should_fire = trigger_fires && !is_suppressed; if trigger_fires is false for every rule, no rule contributes a fact). Adds `CapcoScheme::any_closure_trigger_fires` as a `pub(crate)` inherent method: iterates the catalog and OR's `rule.trigger_fires`. The closure entry path is gated on this; cost is β€24 satisfies calls per projection (8 rules Γ β€3 triggers each), each call walking a tiny constant number of category fields. The closure body's `working.clone()` (heavy Box-of-Box CapcoMarking clone) is now paid ONLY when a productive fixpoint is possible. Tests: - 4 behavioral tests in `crates/capco/tests/closure_runtime.rs` exercising the observable behavior (closure no-op on bottom + uncaveated classified, closure still fires when trigger present, closure runs fixpoint even when suppressed). - 5 predicate-direct tests in `crates/capco/src/scheme/tests.rs` (in-crate so they can call the `pub(crate)` predicate directly per `feedback_pub_doc_hidden_is_still_public_api.md`). Bench: lint_10kb measurement post-commit landed at 1.50ms (median), statistically equivalent to pre-commit 1.51ms β the short-circuit removes work that wasn't actually firing in the bench's no-trigger pages. The R-1 optimization is sound (removes useless work that would otherwise compound on larger / more-loaded inputs) but doesn't attribute the regression source on this bench. Commit 7 attributes and fixes the actual hot spot. Authority: `docs/plans/2026-05-01-lattice-design.md` Β§4.7.3 table-design property (closure-rule monotonicity); D19 B (severity runtime-resolution preserved by leaving `should_fire` itself unchanged).
PR 4b-D.2 commit 7/N. Phase-attribution profiling (new `profile_project` bench) revealed that the lint_10kb regression had two compounding drivers: 1. The page-marking projection cache misses ~50 times per 10KB doc. 2. Portion count grows monotonically per call (1, 2, 3, β¦ 50) β so the per-call O(n) work is paid quadratic-in-portions across the document. The flip-introduced pipeline is structurally heavier than the pre-flip `PageContext::project`: ~15 O(n) lattice walks + 5 tmp_ctx accessor walks vs ~13 PageContext walks pre-flip. Of the two engine-boundary clone rounds the trait path inflicts: - Round #1 β `engine::project_page_marking` wraps each portion in a `CapcoMarking::new(p.clone())` before calling `MarkingScheme::project(&[CapcoMarking])`. - Round #2 β `MarkingScheme::project`'s body extracts `m.0.clone()` for each marking back into `Vec<CanonicalAttrs>`. Both rounds are pure deep-clone allocation cost. This commit adds `CapcoScheme::project_from_attrs_slice(&[CanonicalAttrs])` as a scheme-specific inherent fast-path that consumes the slice shape `PageContext::portions()` already returns, skipping both clone rounds. The trait-path callers (test fixtures, external tooling) continue to go through `MarkingScheme::project`, which now delegates to the same pipeline-body via a shared `project_attrs_pipeline(&[CanonicalAttrs]) -> CanonicalAttrs` internal method. Engine's `project_page_marking` calls the fast path directly: `scheme.project_from_attrs_slice(page_context.portions())`. The closure-rewrite-application sentinel (decisions.md D23) moves from the trait body to `project_attrs_pipeline` β the shared pipeline-step body β so both entry points are sentinel-guarded. Bench: lint_10kb dropped from 1.51ms (post-flip pre-perf) to 1.20ms (after commit 6 short-circuit + this commit's fast-path), a -310Β΅s improvement that closes ~52% of the regression vs the 914Β΅s baseline. A new `profile_project` bench at `crates/engine/benches/profile_project.rs` captures the per-phase attribution (join_via_lattice, closure, scheme.project, from_canonical, end-to-end engine path, full lint, and per-portion-count scaling) so future perf work has a baseline. Authority: PR 4b-D.2 architect's R-1 + (b) hot-spot mitigations called out in the PR brief; `project_perf_baseline_pr5_trigger.md` project memory (routine perf-bumping permitted through PR 5; dedicated perf-analysis work scheduled for PR 5+ if structural regression survives).
PR 4b-D.2 commit 8/N. Eliminates the third (innermost) clone round
the post-flip projection pipeline was paying.
Pre-fix flow (commit 7 fast-path):
engine::project_page_marking
β CapcoScheme::project_from_attrs_slice(&[CanonicalAttrs])
β project_attrs_pipeline(&[CanonicalAttrs])
β CapcoMarking::join_via_lattice(&[CanonicalAttrs])
β build tmp_ctx via add_portion(p.clone()) for p in portions β nΓclone
The engine ALREADY owns a `&PageContext` (the accumulator that
add_portion is filling across the document). Rebuilding tmp_ctx
internally was a redundant deep-clone per portion. Phase-attribution
profiling found tmp_ctx rebuild at ~2.8Β΅s / n=50 portions; cumulated
across the bench's monotone-growing call sequence (sum_i=1^50) this
was ~70Β΅s of pure clone overhead.
Post-fix flow:
engine::project_page_marking
β CapcoScheme::project_from_page_context(&PageContext) β new
β project_attrs_pipeline_with_context(&[attrs], &PageContext)
β CapcoMarking::join_via_lattice_with_context(&[attrs], &PageContext) β new
The engine's existing PageContext is borrowed through to
`join_via_lattice`'s residue-axis accessor calls. No new clones.
Trait path back-compat: `MarkingScheme::project` continues to call
`project_from_attrs_slice`, which (since the engine no longer uses
it) is now the no-pre-built-context entry. It builds a one-shot
tmp_ctx and delegates to the with-context pipeline. Same cost as
before for trait-path callers; only the engine fast-path benefits.
The new `join_via_lattice_with_context` carries a debug-mode
assertion that `portions` and `page_ctx.portions()` are the same
slice β caller's contract. Mismatched inputs would mix per-axis
lattice results from one slice with residue-axis accessor results
from another, which is a semantically-corrupt projection. Production
callers go through the new fast-path entries that pass the same
slice to both arguments; the assertion guards against future
refactor-induced contract violations.
Bench results (lint_10kb, WSL2 dev worktree):
- Pre-flip baseline (origin/staging): 914Β΅s (GHA ubuntu-latest)
- Post-flip unmitigated (after commit 5): 1.51ms (+65%)
- After commit 6 short-circuit: 1.51ms (no change β closure
already short-circuited)
- After commit 7 fast-path: 1.20ms (-310Β΅s)
- After commit 8 (this commit): 1.03ms (-170Β΅s)
- Cumulative savings: -480Β΅s
Final: lint_10kb median 1033Β΅s on WSL2 (vs 914Β΅s baseline, +13%).
Within the +20% acceptable-band per the PR brief's tier ladder
(1005-1100Β΅s band: refresh baseline.json with documented rationale).
WSL2 typically reads 5-10% slower than GHA hosted runners per the
baseline doc's reference-machine note; the GHA re-capture may land
back in the +10% noise band.
Per-phase attribution at n=50 portions (after this commit):
- join_via_lattice + tmp_ctx (now zero-clone): ~7.5Β΅s
- closure (short-circuited): ~70ns
- page_rewrites: ~3.5Β΅s
- from_canonical bridge: ~37ns
- total project + bridge call: 9.7Β΅s
Per-phase attribution at n=2 portions:
- phase_a join_via_lattice: 541ns
- phase_b closure: 72ns
- phase_c scheme.project (trait path): 988ns
- phase_d from_canonical: 37ns
- phase_e engine path (project_from_page_context + bridge): 799ns
- phase_f full lint_10kb: 1.04ms
Authority: PR 4b-D.2 architect's R-1 + (b) tmp_ctx-elimination
mitigation called out in the PR brief; `project_perf_baseline_pr5_trigger.md`
project memory (further perf-analysis scheduled for PR 5+ if
structural cost survives the optimization rounds β this commit
closes the structural-cost gap to within +20% of baseline, in the
"acceptable" band, so PR 5 perf work focuses on other surfaces).
PR 4b-D.2 commit 9/N. Refreshes `benches/baseline.json::lint_10kb`
from {912, 913, 914}Β΅s (pre-flip, PR #498 capture) to {1030, 1033,
1036}Β΅s (post-flip, post-commit-8 optimization stack).
The flip moves the page-marking projection driver from
`PageContext::project()` to `scheme.project(Scope::Page, ...) β
join_via_lattice β closure β page_rewrites`. The new pipeline has
~50% more O(n) walks per call than the pre-flip path (10 lattice
constructors + closure + page rewrites vs 13 expected_* accessors),
which is the structural cost of the hot-path flip. Commits 6-8
brought the regression from +65% unmitigated to +13% post-perf-work:
- Commit 6: closure short-circuit on empty cone triggers
(architect's R-1)
- Commit 7: engine-side `project_from_attrs_slice` fast-path
- Commit 8: PageContext-borrowing `project_from_page_context`
fast-path that eliminates the inner tmp_ctx rebuild's clone round
Captured on dev WSL2 worktree. Per the reference-machine note in the
baseline doc, GHA `ubuntu-latest` typically reads 5-10% faster than
WSL2; the GHA re-capture is expected to land 50-100Β΅s lower than
this WSL2 capture once CI runs against this branch.
PR 4b-E (PageContext deletion) will retire the remaining residue-
axis tmp_ctx-via-PageContext-accessors path, expected to bring the
value back down to baseline-ish. Per
`project_perf_baseline_pr5_trigger.md` project memory, further perf-
analysis work is scheduled for PR 5+ if structural costs survive;
PR 4b-D.2 stays within the +20% acceptable band the brief specified,
so PR 5 perf work can focus on other surfaces.
Brief tier ladder:
- Ideal (β€1005Β΅s / +10% of pre-flip baseline): not reached.
- Acceptable (1005-1100Β΅s / +20% of pre-flip baseline): yes β
1033Β΅s on WSL2.
- Not acceptable (>1100Β΅s): no.
The SC-001 constitutional 16ms absolute ceiling
(`target_upper_ci_us = 16000`) remains the load-bearing gate and is
unaffected by the re-capture.
PR 4b-D.2 commit 10/10. Combines 5 MEDIUM + 2 LOW reviewer items into one cleanup commit. No behavior change. MEDIUM-1 (rust): Stale module-level doc in `crates/capco/src/scheme/marking.rs` claimed `JoinSemilattice` "delegates join to `PageContext::add_portion` per PR 4b-B's plan". Post-flip this is the opposite of true. Rewrote the module doc header to describe the lattice path as the production page- aggregation surface and PageContext as the residue-axis bridge still active until PR 4b-E retires it. Cites docs/plans/2026-05-01-lattice-design.md Β§4.7.4 + Constitution VII Β§IV. MEDIUM-2 (general): Stale `CategoryAction::Promote` arm comment in `crates/capco/src/scheme/marking_scheme_impl.rs` claimed "engine.lint does not drive aggregation through project() yet" β PR 4b-D.2 just reversed that. Rewrote to: engine now drives through `project()`; Promote stays a no-op because JOINT-promotion and FGI-absorption are renderer-canonical territory (`render_canonical`'s job, not the projection lattice's). Cites docs/plans/2026-05-01-lattice-design.md Β§10 row 4 (SCI per-system canonicalization) + Β§10 row 5 (SAR ordering). Behavioral conclusion (no-op) unchanged. MEDIUM-3 (rust): Added a structural-size doc comment + permanent `#[allow(clippy::too_many_lines, reason = "...")]` on `join_via_lattice_body`. The 129-LOC function is structurally justified β splitting would either thread cross-axis state via a struct (paying the cost it notionally saved) or scatter the inline Β§-citations across files (Constitution VIII harm). Future maintainers hitting the lint should `#[allow]`, not split. `cargo +stable clippy --workspace --tests -- -D warnings` clean both with and without `-A clippy::too_many_lines`. MEDIUM-4 (rust+general): Expanded the module doc of `crates/engine/benches/profile_project.rs` to spell out which phases use full `Engine::lint` (Phase F) versus synthesized `CanonicalAttrs` portions (Phases A-E, G-I). The synthesis is a phase-attribution probe, not a regression gate. Documented the maintenance contract: if the bench corpus's representative axis mix drifts, regenerate the synthesis. Tagged as "maintenance item, not a bug." MEDIUM-5 (general): Downgraded `CapcoScheme::project_from_attrs_slice` from `pub` to `pub(crate)`. Post-commit-8, the engine's `project_page_marking` calls `project_from_page_context` exclusively; the only remaining caller of `project_from_attrs_slice` is the in-crate trait-path delegate for `MarkingScheme::project`. Verified no out-of-crate callers via `cargo +stable check --workspace --tests` (clean). Added a `## Visibility` doc section documenting the downgrade and the promote-back criteria. LOW (baseline.json): Captured the WSL2-vs-GHA disparity in the `reference_machine._dev_capture_note` field + added a `lint_10kb._wsl2_dev_capture` sub-object preserving the WSL2 measurement as a reference point. The first GHA re-capture of this branch via `scripts/capture-baselines.sh` overwrites the top-level numbers; the sub-object stays as cross-host context. JSON validity verified. LOW (closure_hotpath): Renamed `project_is_idempotent_on_classified_orcon` to `project_pipeline_is_idempotent_on_orcon_derived_closed_state`. The test actually verifies idempotence on the NOFORN-injected closed state (not on ORCON-only state), so the prior name was misleading. Updated the doc comment to match. DEFERRED to follow-up (NOT included): - Test helper duplication across `closure_hotpath.rs` and `lattice_corpus.rs` β extract to `tests/common.rs` in a follow-up test-only PR. - CLOSURE_NOFORN_UCNI DoD coverage gap β pre-existing per #407. - Β§H.7 pp123-125 citation precision β pre-existing from PR 4b-B, renderer-territory deferred to PR 5+. Verification gates (final): - cargo +stable build --workspace: clean - cargo +stable clippy --workspace --tests -- -D warnings: clean - cargo +stable test --workspace: 0 failures
CI's `cargo fmt --check` flagged 5 files. The reviewer-feedback cleanup commit (`d33e14e1`) was verified via build/clippy/test locally but not against `fmt --check`. Pure formatting; no behavior change. Affected files: - crates/capco/src/scheme/actions/intent.rs - crates/capco/src/scheme/marking_scheme_impl.rs - crates/capco/tests/closure_runtime.rs - crates/engine/benches/profile_project.rs - crates/engine/tests/closure_hotpath.rs
Contributor
There was a problem hiding this comment.
Pull request overview
This PR flips CAPCO page projection and engine page-marking hot paths from PageContext aggregation to lattice aggregation with closure and page rewrites, while adding runtime and benchmark coverage for the new path.
Changes:
- Adds
ProjectedMarking::from_canonicaland routes engine page-marking projection throughCapcoScheme::project_from_page_context. - Reworks
CapcoMarking/CapcoSchemeprojection to usejoin_via_lattice β closure β page_rewrites. - Adds closure hot-path tests, lattice corpus fixtures, perf profiling bench, and updates baseline/context docs.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
specs/006-engine-rule-refactor/decisions.md |
Adds D22/D23 decisions for NOFORN supersession and closure sentinel placement. |
crates/ism/src/projected.rs |
Adds ProjectedMarking::from_canonical and unit tests. |
crates/engine/tests/lattice_corpus.rs |
Adds Pattern-D lattice projection integration tests. |
crates/engine/tests/closure_hotpath.rs |
Adds closure-on-hot-path engine-facing tests. |
crates/engine/src/engine.rs |
Routes page marking projection through scheme/lattice fast path. |
crates/engine/Cargo.toml |
Registers the new profile_project benchmark. |
crates/engine/benches/profile_project.rs |
Adds projection phase attribution benchmark. |
crates/capco/tests/page_context_lattice_parity.rs |
Retargets parity fixtures for post-flip scheme/lattice behavior. |
crates/capco/tests/closure_runtime.rs |
Adds closure short-circuit behavioral tests. |
crates/capco/src/scheme/tests.rs |
Adds in-crate tests for closure trigger short-circuit predicate. |
crates/capco/src/scheme/marking.rs |
Flips JoinSemilattice::join and adds PageContext-borrowing lattice path. |
crates/capco/src/scheme/marking_scheme_impl.rs |
Implements lattice + closure + rewrite projection pipeline and fast paths. |
crates/capco/src/scheme/actions/page_context.rs |
Marks old PageContext projection bridge as non-production/dead-code. |
crates/capco/src/scheme/actions/mod.rs |
Keeps PageContext projection re-export suppressed until deletion. |
crates/capco/src/scheme/actions/intent.rs |
Adds NOFORN supersession routing for FactAdd. |
crates/capco/CAPCO-CONTEXT.md |
Updates CAPCO context for post-flip divergence inventory. |
benches/baseline.json |
Refreshes lint_10kb baseline/provenance notes. |
π‘ Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+706
to
+709
| CapcoMarking::new(CapcoMarking::join_via_lattice(&[ | ||
| self.0.clone(), | ||
| other.0.clone(), | ||
| ])) |
Comment on lines
+179
to
+186
| /// semantically-corrupt projection. Debug-mode assertion below | ||
| /// guards this at test time. | ||
| pub fn join_via_lattice_with_context( | ||
| portions: &[CanonicalAttrs], | ||
| page_ctx: &marque_ism::PageContext, | ||
| ) -> CanonicalAttrs { | ||
| #[cfg(debug_assertions)] | ||
| debug_assert_eq!( |
Comment on lines
+4221
to
+4225
| /// Project the current [`marque_ism::PageContext`] into a | ||
| /// [`marque_ism::ProjectedMarking`] via the scheme's production | ||
| /// page-projection path. | ||
| /// | ||
| /// PR 4b-D.2 flipped the hot path from `PageContext::project()` (the |
Comment on lines
+19
to
+25
| //! Closure injects USA/NATO into REL TO; the lattice state shows | ||
| //! USA + NATO membership. S007 fires through `Engine::lint`'s | ||
| //! NDJSON path at `Severity::Suggest` β but this file does NOT | ||
| //! exercise `Engine::lint` (the strict-recognizer fixture surface | ||
| //! for S007's text-layer behavior lives in | ||
| //! `crates/capco/tests/dissem_nato_*.rs`); it pins the lattice | ||
| //! layer's injection observable. |
Comment on lines
+11
to
+13
| "lower_ci_us": 1030, | ||
| "mean_us": 1033, | ||
| "upper_ci_us": 1036, |
Comment on lines
+233
to
+239
| // CapcoMarking values. The engine's hot path bypasses | ||
| // this via `CapcoScheme::project_from_attrs_slice` | ||
| // (inherent method below), which consumes | ||
| // `&[CanonicalAttrs]` directly and shares the rest of | ||
| // the pipeline. Test fixtures and external tooling | ||
| // continue to use this trait-path entry; the engine | ||
| // takes the fast path. |
| dissem_nato: attrs.dissem_nato, | ||
| non_ic_dissem: attrs.non_ic_dissem, | ||
| rel_to: attrs.rel_to, | ||
| display_only_to: attrs.display_only_to, |
Comment on lines
+84
to
+87
| /// Extract the set of per-portion `CanonicalAttrs` produced by the | ||
| /// engine's strict pipeline on the bench input. We lint once and grab | ||
| /// the page-context portions; replay them under the per-phase | ||
| /// micro-benches below. |
Comment on lines
+207
to
+209
| /// Clippy's `too_many_lines` lint fires on this function at 129 | ||
| /// LOC vs the 100-line default. The size is structurally | ||
| /// justified β splitting would harm correctness: |
| let portion_attrs = [attrs.clone()]; | ||
| let dissem_set = | ||
| crate::lattice::DissemSet::from_attrs_iter(&portion_attrs).with_noforn_injected(); | ||
| attrs.dissem_us = dissem_set.into_boxed_slice(); |
β¦elax MarkingScheme bound (Copilot R1 #1) PR 4b-D.2 commit 11 / decisions.md D24. Public API change in `marque-scheme`: relax `MarkingScheme::Marking: JoinSemilattice` to `MarkingScheme::Marking` and relax `DiffInput<M: JoinSemilattice>` to `DiffInput<M>`. Drop `impl JoinSemilattice for CapcoMarking` and `impl MeetSemilattice for CapcoMarking`. ## Why Copilot R1 surfaced an idempotence-law violation: `m.join(&m) != m` when `m.rel_to = [NATO]` because `RelToBlock::from_attrs_iter` expands NATO via `lookup_tetragraph_members` to its 30 trigraph members; structural `Eq` on `CanonicalAttrs` fails after the cross-axis fold. The lattice consultant's verdict (Option D-extended) was: the per-axis lattices (`RelToBlock`, `DissemSet`, `SciSet`, etc.) ARE sound lattices on their native domains (the BTreeSet-of-expanded- trigraphs representative is canonical post-expansion); `CapcoMarking` is a **cross-axis fold** that composes those lattice values back into a `CanonicalAttrs` record. Cross-axis folding is a *projection*, not a lattice op. Claiming `JoinSemilattice` on the record type promised a law (structural-`Eq` idempotence) the construction cannot keep without either (a) lossy eager canonicalization at construction (loses the renderer's `NATO` atom; non-unique inverse), or (b) quotient-`Eq` rewrite across every `CanonicalAttrs` field (massive blast radius). Both rejected. The trait-bound relaxation here is the surgical fix: remove the false claim. The cross-axis fold remains accessible as the inherent methods `CapcoMarking::join_via_lattice` and `CapcoMarking::join_via_lattice_with_context` (engine's `project_from_page_context` hot path uses the latter β unchanged). ## Q2 audit β 151 `CapcoMarking::new` call sites Verified: no production site constructs `CapcoMarking` from non-canonical input. The 151 sites break down as: - Production engine paths (decoder.rs, engine.rs): ~27 sites, all consuming parser-produced `CanonicalAttrs` (the parser emits tetragraph atoms verbatim β `NATO` stays `NATO`, not expanded β which is the correct representation for the renderer and the input shape the lattice operates on). - Production scheme paths (capco/src/scheme/*.rs): 5 sites, all consuming either `CanonicalAttrs::default()` or `join_via_lattice`-produced output (already in expanded-canonical form). - Test/bench paths (capco/tests/, capco/src/scheme/tests.rs, engine/tests/, engine/benches/): ~119 sites, all using either `default()`, parser output, or hand-crafted fixtures where the tetragraph form is intentional (`(S//REL TO USA, NATO)` is a valid input shape and the test is asserting the lattice handles it correctly). No call site produces a marking that "bypasses canonicalization unexpectedly" β the input shape (with tetragraph atoms) is the right shape; the bug is the trait claim, not the data. Confirms the consultant's verdict that Option A (eager canonicalization at construct) would be the wrong fix. ## Q3 follow-up β issue #528 The systematic audit of the remaining per-axis types for structural-vs-lattice-`Eq` mismatches (`DissemSet::relido_observed_unanimous`, `JointSet::Mixed`/`DisunityCollapse`, `SupersessionSet`) is deferred to issue #528 per Constitution VII scope-discipline. PR #456 already split `Lattice` into Join/Meet halves and identified those three as having observational state; this PR's scope is the cross-axis fold (`CapcoMarking`), not the per-axis audit. ## Test impact - `crates/capco/tests/scheme_equivalence.rs`: - `lattice_join_agrees_with_project_banner_pairwise` renamed to `join_via_lattice_agrees_with_project_banner_pairwise` and pivoted to use the inherent method instead of the trait. - `capco_marking_meet_narrow_components` + `capco_marking_meet_with_missing_classification_is_none` removed (exercised the unsound trait impl; no production code consumed the trait method). - `crates/capco/tests/proptest_lattice.rs`: 10 new proptest cases added covering `RelToBlock` join + meet laws + absorption + bottom identity. These pin the lattice claim at the algebraically-sound site β the per-axis type's own structural `Eq` (the BTreeSet-of- expanded-trigraphs representative). All pass (38/38). Other ~149 sites: zero impact (none called `.join()` / `.meet()` on a `CapcoMarking`; the trait was purely declarative). ## Verification gates cargo +stable fmt --all --check : clean cargo +stable build --workspace : clean cargo +stable clippy --workspace ... : clean cargo +stable test --workspace : 0 failures ## References - Lattice consultant verdict (this session): "per-axis lattices are real; the cross-axis composition is structural folding, not a lattice operation" - PR #456 (Join/Meet split + observational-state framing) - `marque-applied.md` Β§3 (PR 3b stall walkthrough) - `pure-lattice.md` Β§7 (quotient lattices) + Β§11 (powerset Boolean algebra) - Tracking issue: #528
PR 4b-D.2 commit 12. Combines the 9 Copilot-R1 items not covered by commit 11 (D24 trait-bound relaxation). No behavior change beyond item #2 (new PageRewrite + new CategoryId); items 3-10 are doc/visibility/test/calibration nits. ## Item 2 β NOFORN clears `display_only_to` (correctness) Verified Copilot's claim: `capco/noforn-clears-rel-to` exists (at `crates/capco/src/scheme/rewrites/noforn_clears.rs:67`); no `noforn-clears-display-only-to` equivalent shipped. The companion `capco/noforn-clears-fdr-family` strips the `Displayonly` TOKEN from `dissem_us` but does NOT clear `attrs.display_only_to` (the country-list field). When closure injects NOFORN AFTER a portion populated `display_only_to` via the per-portion union, the renderer would emit an inconsistent banner β Β§H.8 p145 violation. Fix: - Added `CAT_DISPLAY_ONLY_TO = CategoryId(12)` in `crates/capco/src/scheme/mod.rs` so the rewrite can use the symmetric `CategoryAction::Clear { CAT_DISPLAY_ONLY_TO }` shape (mirrors `capco/noforn-clears-rel-to`). - Wired the new CategoryId through `capco_category_clear` + `capco_category_has_values`. - Added `capco/noforn-clears-display-only-to` PageRewrite at declaration order index 15 (between the existing two NOFORN-clears rows and the transmutation rewrites). Citation: CAPCO-2016 Β§H.8 p145 + Β§D.2 Table 3 rows 1-2 (verified against `crates/capco/docs/CAPCO-2016.md` at authoring). - Updated rewrite-count pins: 23 β 24 in `scheme_declares_phase3_rewrites` and `engine_construction_succeeds_with_full_rewrite_table`. - Added 2 closure_hotpath tests: `noforn_clears_display_only_to_via_cross_portion_join` and `noforn_clears_display_only_to_is_idempotent_on_empty_field`. ## Item 3 β baseline.json CI calibration Copilot was right: `scripts/bench-check.sh` consumes top-level `lint_10kb.{lower,mean,upper}_ci_us` for the CI regression gate; calibrating to the WSL2 dev-capture (1030/1033/1036) would mask regressions on faster GHA hardware. Reverted top-level to the pre-PR GHA baseline (912/913/914). WSL2 measurement preserved in the `_wsl2_dev_capture` sub-object for record-keeping. Follow-up: GHA re-capture via `scripts/capture-baselines.sh` produces the post-flip authoritative numbers after merge. ## Item 4 β `join_via_lattice_with_context` visibility Downgraded `pub` β `pub(crate)`. The same-slice contract on `portions` / `page_ctx.portions()` is only verified under `#[cfg(debug_assertions)]`; promoting to `pub` without a release-mode guard would invite cross-crate callers to violate the contract silently. The two production callers (engine's `project_attrs_pipeline_with_context` and the `join_via_lattice` wrapper) are in-crate. ## Item 5 β `engine.rs` doc-comment attribution Moved `project_page_marking` to AFTER `dispatch_page_finalization` (was before). Pre-fix the dispatch function's `# Returns` doc-block ran directly into the helper's doc, attributing wrongly. Post-fix each function's doc is immediately above its `fn` declaration. ## Item 6 β `lattice_corpus.rs` file-level doc contradiction Rewrote the `closure_nato_rel_to_us_plus_nato` summary. Pre-fix it claimed "Closure injects USA/NATO into REL TO" which is the OPPOSITE of what the test asserts (`!rel_to_contains` for both USA and NATO, because the Β§H.7 reciprocal-raise flattens classification to `Us(_)` BEFORE closure observes the `TOK_NATO_CLASS` trigger). Updated to describe the actual suppression mechanic and the FGI-axis survival. ## Item 7 β stale comment about `project_from_attrs_slice` The comment in `marking_scheme_impl.rs:239` claimed `project_from_attrs_slice` is callable cross-crate. It's `pub(crate)` post-commit-10, called only by the in-crate trait body. Updated to reflect the two distinct fast-path entries (`project_from_page_context` for engine, `project_from_attrs_slice` for in-crate non-PageContext callers). ## Item 8 β `join_via_lattice_body` size note accuracy Copilot was right: the function spans lines 284-706 (~423 LOC) in the current revision, not the ~129 LOC the prior doc claimed. The 129 figure was wrong on inspection β the body has always been ~420 LOC since PR 4b-B Commit 7 added the per-axis composition. Updated the doc to reflect the actual size; the structural justification (axis ordering + inline citations + cross-axis state flow) is even stronger at the actual size. ## Item 9 β `display_only_to` unit test gap Added 3 unit tests in `crates/ism/src/projected.rs::from_canonical_*`: single-country preservation, multi-country preservation, empty preservation. The pre-fix surface covered `rel_to` but not the parallel `display_only_to` country-list axis. ## Item 10 β stale `profile_project.rs` local function comment Rewrote the `collect_portions` doc. Pre-fix it claimed "extract portions produced by the strict pipeline" but the body synthesizes a representative `(S//NF)` + `(TS//SI)` pair AFTER an Engine::lint warmup call. Updated to describe the actual synthesis intent and explain the Engine::lint call is a warmup (not an extraction). ## Verification gates cargo +stable fmt --all --check : clean cargo +stable build --workspace : clean cargo +stable clippy --workspace ... : clean cargo +stable test --workspace : 0 failures
Contributor
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 26 out of 26 changed files in this pull request and generated 13 comments.
Comments suppressed due to low confidence (2)
crates/capco/src/scheme/rewrites/noforn_clears.rs:187
- This adds a third row to
noforn_clears_rows, but the function-level comments above still describe βthe two NOFORN-clears rowsβ and list only the first two. Please update that local inventory so future readers don't miss this rewrite when reasoning about declaration order.
// `capco/noforn-clears-display-only-to` β companion to
// `capco/noforn-clears-rel-to` for the `display_only_to`
// country-list axis. PR 4b-D.2 Copilot R1 #2: pre-fix,
// closure-injected NOFORN on a portion that also carried
// DISPLAY ONLY USA, GBR left `attrs.display_only_to`
// populated even though NOFORN had landed in `dissem_us`
// (the `fdr-family` row above strips the token but the
// country list is a separate field). The renderer would
// then emit an inconsistent banner per Β§H.8 p145 ("NOFORN
// ... Cannot be used with REL TO / RELIDO / EYES ONLY /
// DISPLAY ONLY") + Β§D.2 Table 3 rows 1-2 (NOFORN dominates
// the FD&R family).
//
// Uses `CategoryAction::Clear { CAT_DISPLAY_ONLY_TO }`
// symmetrically with the REL TO clearer above; the
// `CAT_DISPLAY_ONLY_TO` CategoryId was added in PR 4b-D.2
// Copilot R1 #2 (`crates/capco/src/scheme/mod.rs`) and
// routed through `capco_category_clear` /
// `capco_category_has_values`.
PageRewrite::declarative(
crates/capco/src/scheme/actions/intent.rs:282
- This NOFORN FactAdd path only rebuilds
dissem_us; it does not clear the parallel FD&R axes such asattrs.rel_toorattrs.display_only_to. That is fine whenscheme.projectlater runs the NOFORN-clears PageRewrites, but directapply_intentfixes (E021/E038-style FactAdd) return immediately from here and can render a marking with newly-added NOFORN plus an existing REL TO/DISPLAY ONLY country list, still violating Β§H.8 p145. The injection-site fix needs to clear those fields too, or the comment should not claim the invariant is satisfied for direct rule-driven FactAdd paths.
if target == DissemControl::Nf {
let portion_attrs = [attrs.clone()];
let dissem_set =
crate::lattice::DissemSet::from_attrs_iter(&portion_attrs).with_noforn_injected();
attrs.dissem_us = dissem_set.into_boxed_slice();
return Ok(());
Comment on lines
+722
to
+729
| debug_assert_eq!( | ||
| raw, | ||
| raw_snapshot.as_slice(), | ||
| "closure() mutated the per-portion CanonicalAttrs slice β \ | ||
| violates PageRewrite read-only-attrs invariant \ | ||
| (docs/plans/2026-05-01-lattice-design.md Β§3 (e.1))" | ||
| ); | ||
|
|
Comment on lines
+64
to
+72
| /// 5. **DISPLAY-ONLY / FD&R-family (2):** | ||
| /// `capco/noforn-clears-fdr-family` (strips DISPLAY ONLY / | ||
| /// RELIDO / EYES tokens from `dissem_us`) at Β§D.2 Table 3 | ||
| /// row 2 + Β§H.8 p154 + Β§H.8 p157, plus | ||
| /// `capco/noforn-clears-display-only-to` (PR 4b-D.2 Copilot R1 | ||
| /// #2 β clears `attrs.display_only_to`, the country-list | ||
| /// sibling of `attrs.rel_to`) at Β§H.8 p145 + Β§D.2 Table 3 | ||
| /// rows 1-2. The two rows together close the parallel REL TO / | ||
| /// DISPLAY ONLY axes. |
Comment on lines
+229
to
+234
| debug_assert_eq!( | ||
| portions, | ||
| page_ctx.portions(), | ||
| "join_via_lattice_with_context: portions slice and page_ctx \ | ||
| portions() must be the same slice β caller's contract." | ||
| ); |
Comment on lines
+15
to
+23
| "_wsl2_dev_capture": { | ||
| "lower_ci_us": 1030, | ||
| "mean_us": 1033, | ||
| "upper_ci_us": 1036, | ||
| "profile": "WSL2 dev worktree, x86_64", | ||
| "date_captured": "2026-05-17", | ||
| "_note": "PR 4b-D.2 post-commit-8 WSL2 measurement (median 1033Β΅s). Captured during PR 4b-D.2 perf-mitigation work for record-keeping; **NOT the authoritative baseline**. The top-level `lint_10kb.{lower,mean,upper}_ci_us` fields are the GHA `ubuntu-latest` reference values that `scripts/bench-check.sh` consumes for the CI regression gate. The CI gate MUST be calibrated to GHA hardware β calibrating to WSL2 (5-10% slower) would mask regressions on faster GHA runners. **Follow-up**: the GHA re-capture via `scripts/capture-baselines.sh` after PR 4b-D.2 merges produces the post-flip authoritative numbers and updates the top-level fields." | ||
| }, | ||
| "_note": "Values are Criterion 95% confidence-interval bounds. **2026-05-17 PR 4b-D.2 hot-path-flip context.** Per Copilot R1 review #3 these top-level fields hold the pre-PR GHA `ubuntu-latest` baseline (912/913/914 from PR #498), not the WSL2 dev-capture from the PR 4b-D.2 perf-mitigation work β the CI regression gate at `scripts/bench-check.sh:32-35` consumes these fields and must be calibrated to the same hardware where CI runs. The WSL2 measurement is preserved in `_wsl2_dev_capture` for cross-host context. **Post-PR-4b-D.2 expectation**: the new pipeline IS structurally heavier than the pre-flip path (~50% more O(n) walks per call across 10 lattice constructors + closure + page rewrites vs PageContext's 13 `expected_*` accessors; PageContext stays alive as the page-state accumulator until PR 4b-E). Commits 6-8 brought the WSL2 measurement from +65% unmitigated to +13% via three optimizations: (6) closure short-circuit on empty cone triggers (architect's R-1); (7) engine-side fast-path `project_from_attrs_slice`; (8) `project_from_page_context` borrowing the engine's existing PageContext through to `join_via_lattice_with_context`. The first GHA re-capture after merge updates these numbers and the CI gate moves with it. Per the `project_perf_baseline_pr5_trigger.md` project memory, further perf-analysis work is scheduled for PR 5+ if the structural costs survive the GHA re-capture; PR 4b-E (PageContext deletion) will retire the remaining residue-axis tmp_ctx requirement, expected to bring the GHA value back down. **2026-05-17 re-capture (PR #498).** Refreshed lower/mean/upper to {912, 913, 914} on `ubuntu-latest` GHA runner (PR #498 CI run 25991729674), 3Β΅s over the previous 911Β΅s threshold; the new threshold (`upper_ci_us * 1.10` β 1005Β΅s) restores the +10% noise envelope. **2026-05-05 widening (D8 amendment) β superseded by the 2026-05-17 re-capture.** Original 2026-04-26 baseline (lower 746 / mean 749 / upper 753) flapped on shared GHA `ubuntu-latest` runners; per D8 `decisions.md`, the +10% widening (746β821, 749β824, 753β828) held until staging complexity additions exceeded the noise band. The SC-001 16ms absolute target remains the load-bearing constitution gate; CI-side regression-gate calibration is independent.", |
Comment on lines
+237
to
+239
| per-axis lattice types; PR 4b-D.2 (2026-05-17) flipped the | ||
| production `JoinSemilattice::join` and `MarkingScheme::project` to | ||
| delegate to that lattice path. The parity gate at |
Comment on lines
1212
to
+1215
| page_marking_arc | ||
| .get_or_insert_with(|| Arc::new(page_context.project())) | ||
| .get_or_insert_with(|| { | ||
| Arc::new(project_page_marking(&self.scheme, &page_context)) | ||
| }) |
Comment on lines
+225
to
+227
| // The closure operator and PageRewrites are both | ||
| // monotone, and PageRewrites operate on the closed | ||
| // state's remaining tokens. |
Comment on lines
+59
to
+65
| // The rewrite is needed because the closure operator (e.g. | ||
| // `CLOSURE_NOFORN_SAR` on a portion that ALSO carries DISPLAY | ||
| // ONLY USA, GBR) injects NOFORN AFTER `join_via_lattice` has | ||
| // set `attrs.display_only_to` from the per-portion union. | ||
| // Without this rewrite the renderer would emit an inconsistent | ||
| // banner: NOFORN in `dissem_us` AND a populated | ||
| // `display_only_to` country list, violating Β§H.8 p145. |
Comment on lines
+444
to
+454
| fn noforn_clears_display_only_to_via_cross_portion_join() { | ||
| let usa = CountryCode::USA; | ||
| let gbr = CountryCode::try_new(b"GBR").expect("trigraph"); | ||
|
|
||
| // Portion 1: NOFORN. Portion 2: DISPLAY ONLY USA, GBR. | ||
| let nf_portion = classified_with_dissem(Classification::Secret, DissemControl::Nf); | ||
| let mut do_portion = classified_us(Classification::Secret); | ||
| do_portion.dissem_us = vec![DissemControl::Displayonly].into_boxed_slice(); | ||
| do_portion.display_only_to = vec![usa, gbr].into_boxed_slice(); | ||
|
|
||
| let projected = project_page(&[nf_portion, do_portion]); |
Comment on lines
+265
to
+276
| // The other FactAdd targets (Relido, Displayonly, Oc, OcUsgov) | ||
| // do NOT need supersession routing: Β§H.8 p145 only specifies | ||
| // NOFORN as a dominator on the FD&R chain. The OC-vs-OC-USGOV | ||
| // Β§H.8 p136/p140 supersession runs at join time (where both | ||
| // tokens can be observed on different portions); FactAdd of | ||
| // OcUsgov alongside existing Oc is a per-portion config that | ||
| // the lattice will resolve at the next join. | ||
| // | ||
| // Authority: Β§H.8 p145 (NOFORN: "Cannot be used with REL TO, | ||
| // RELIDO, EYES ONLY, or DISPLAY ONLY") + Β§D.2 Table 3 rows 1-2 | ||
| // + Β§H.8 p157 (EYES ONLY: NSA-only, retains DissemControl::Eyes | ||
| // through lint per scheme.rs:190). |
β¦site (Copilot R2 suppressed #2 + inverse case) PR 4b-D.2 commit 13. Items 1, 2, 3 from Copilot R2. The D22 fix landed in PR 4b-D.2 commit 3 routed NOFORN FactAdd through DissemSet's token-axis supersession overlay. Copilot R2 surfaced two completion gaps the original commit missed: (a) Country-list axes stayed populated. `apply_fact_add` evicted `Rel` / `Relido` / `Displayonly` / `Eyes` TOKENS from `dissem_us` but did NOT clear the parallel `attrs.rel_to: Box<[CountryCode]>` / `attrs.display_only_to: Box<[CountryCode]>` country-list fields. The closure / PageRewrite paths cleared them via the `capco/noforn-clears-rel-to` / `capco/noforn-clears-display-only-to` rewrites; but direct `apply_intent` callers (E021 AEA β NOFORN, E038 NODIS/EXDIS β NOFORN) bypass `scheme.project`'s PageRewrite loop and got a marking with NOFORN + populated country lists β invalid per Β§H.8 p145. (b) Inverse case: FactAdd of `RELIDO` / `DISPLAY ONLY` / `EYES` onto a marking with `dissem_us = [Nf]` was APPENDING the dominated token. Result: `dissem_us = [Nf, Relido]` β same Β§H.8 p145 violation as (a), opposite direction. The existing double-NOFORN-insertion guard didn't fire because target β Nf; the supersession check needed an explicit inverse-case branch. Fix in `crates/capco/src/scheme/actions/intent.rs::apply_fact_add` CAT_DISSEM branch: 1. NEW: after `with_noforn_injected` rebuilds `dissem_us`, unconditionally clear `attrs.rel_to` and `attrs.display_only_to` if they're non-empty. Brings the full Β§H.8 p145 supersession to the injection site, not just the token-axis half. 2. NEW: at the start of the dissem branch, if `target` is one of `Rel` / `Relido` / `Displayonly` / `Eyes` AND `attrs.dissem_us` already contains `Nf`, return `IntentInapplicable` β the caller's FactAdd is dominated by existing state. Matches the existing idempotency guard for double-NOFORN insertion (no mutation, audit log doesn't see an applied no-op). The PageRewrite layer (`capco/noforn-clears-*`) remains as defense-in-depth. With Item 1 in place, the PageRewrites are defensive-redundant on the production path; both layers converge to the same correct Β§H.8 p145 output. Authority: Β§H.8 p145 ("NOFORN ... Cannot be used with REL TO, RELIDO, EYES ONLY, or DISPLAY ONLY") + Β§D.2 Table 3 rows 1-2 + Β§H.8 p157 (EYES ONLY: NSA-only, retains `DissemControl::Eyes` through lint). Re-verified 2026-05-18 against `crates/capco/docs/CAPCO-2016.md`. ## Item 2 β fix the broken display_only_to integration test Copilot R2 #2 was right: the previous `noforn_clears_display_only_to_via_cross_portion_join` test didn't exercise the new `capco/noforn-clears-display-only-to` rewrite. `PageContext::expected_display_only` short-circuits to empty whenever ANY portion has NOFORN (page_context.rs:881-896); with portion 1 NOFORN-bearing, `out.display_only_to` was empty at join time BEFORE the rewrite ran. The test passed regardless of whether the rewrite existed. Renamed to `noforn_clears_display_only_to_via_ucni_promote` and pivoted the fixture to: - Portion 1: classified DOD UCNI + DISPLAY ONLY USA, GBR (the UCNI carries the Β§H.6 p116 strip-and-promote trigger; the DISPLAY ONLY ensures portion 1 passes the row-19 all-or-nothing gate in `expected_display_only`). - Portion 2: classified DISPLAY ONLY USA (also contributes display-permission so the gate doesn't short-circuit). Verification by toggle: - WITH `apply_fact_add` clearing AND the PageRewrite: test passes. - WITHOUT `apply_fact_add` clearing (Item 1 disabled), WITH the PageRewrite: test still passes β PageRewrite catches the violation. - WITHOUT the PageRewrite (Item 1 enabled, rewrite deleted): test passes β `apply_fact_add` catches it. - Hypothetical: WITHOUT BOTH: test fails. Both layers are load-bearing for their respective injection paths. The integration test now asserts the Β§H.8 p145 banner invariant holds through the full `scheme.project` pipeline regardless of which layer cleared. Defense-in-depth coverage documented inline. ## Item 3 β replace misleading example in noforn_clears.rs Copilot R2 #3 was right: `CLOSURE_NOFORN_SAR` cannot be the load-bearing trigger for the `noforn-clears-display-only-to` rewrite β DISPLAY ONLY is in `FDR_DOMINATORS` (closure.rs:95-109) which SUPPRESSES all 7 NOFORN-implies closure rules on any portion that carries DISPLAY ONLY. The realistic trigger is a Pattern-A/C PageRewrite (UCNI promote, NODIS-implies-NF) injecting NOFORN post-join. Rewrote both occurrences (line 65 + line 191 in noforn_clears.rs) to cite the Pattern-C UCNI scenario and explain why closure rules can't be the load-bearing path on this axis. Also documented the post-R2-#1 defense-in-depth framing: `apply_fact_add` is now self-sufficient; the rewrite covers future-refactor cases that bypass it. ## D22 amendment `decisions.md` D22 entry expanded to record the post-R2 completed scope: (a) token-axis eviction (PR 4b-D.2 commit 3); (b) country-axis clearing (Copilot R2 #1 commit 13); (c) inverse-case rejection (Copilot R2 #1 commit 13). All three axes now maintained at the apply_fact_add injection site, with the PageRewrite layer as defense-in-depth. ## Adjacency-walk findings Searched `FactAdd.*TOK_NOFORN|FactRef::Cve(TOK_NOFORN` across crates/ β production callers: - `crates/capco/src/rules_declarative.rs:1096` (E021 AEA add-NF) - `crates/capco/src/rules_declarative.rs:1504` (E038-style add-NF) - `crates/capco/src/scheme/rewrites/pattern_c.rs:219,269` (UCNI promotes) - `crates/capco/src/scheme/rewrites/pattern_a.rs:146,210,289,369` (nodis/exdis/sbu-nf/les-nf-implies-noforn) All 8 production NOFORN FactAdd sites now benefit from self-sufficient apply_fact_add clearing. The two E021/E038-style rules in rules_declarative.rs are the direct-caller paths Item 1's country-axis clearing specifically targets; the 6 Pattern-A/C rewrite paths go through `scheme.project`'s PageRewrite loop already, but Item 1 makes their apply_fact_add invocation self-sufficient too (defense-in-depth at the lower layer). Searched `FactAdd.*TOK_RELIDO|FactAdd.*TOK_DISPLAY_ONLY|FactAdd.*TOK_EYES` β ZERO production sites currently FactAdd dominated tokens directly. The inverse-case rejection is correctness-by-construction for future rule emissions. ## Verification gates cargo +stable fmt --all --check : clean cargo +stable build --workspace : clean cargo +stable clippy --workspace ... : clean cargo +stable test --workspace : 0 failures Specific test runs: - `category_action_intent::apply_fact_add_*` (6 new tests): all pass - `closure_hotpath::noforn_clears_display_only_to_*` (2 tests, rewrote one + kept one): all pass Tracking issue #528 (per-axis lattice Eq audit) remains the follow-up for the systematic `DissemSet`/`JointSet`/`SupersessionSet` review per PR #456's observational-state framing.
PR 4b-D.2 commit 14. Items 4-13 from Copilot R2 review. No
behavior change beyond the G13 sentinel content-ignorance fix
(Item 4); items 6-13 are doc-comment freshness, terminology, and
visibility nits. Item 5 (PR description revert-alignment) is the
sister commit applied via `gh pr edit 527`.
## Item 4 β G13 content-ignorance on closure + same-slice sentinels (HIGH)
Replaced two `debug_assert_eq!` call sites whose default `{:?}`
panic format would dump full `CanonicalAttrs` content (token
canonicals, country codes, declassify dates) on assertion
failure β a G13 audit-content-ignorance violation per Constitution
V Principle V.
- `crates/capco/src/scheme/marking_scheme_impl.rs` (closure-rewrite-
application sentinel at the scheme-side D23 placement): switched
to explicit `if raw != raw_snapshot.as_slice() { panic!("...{}
portion(s) before vs {} after...", raw_snapshot.len(), raw.len());
}`. Emits portion counts only.
- `crates/capco/src/scheme/marking.rs` (same-slice contract check in
`join_via_lattice_with_context` between `portions` and
`page_ctx.portions()`): same pattern β count-only panic mirroring
`engine.rs::check_portions_unchanged` at lines 4540-4574.
Both sentinels stay `#[cfg(debug_assertions)]` (release-mode
elision unchanged); the change is panic-format-only. Pattern
matches the existing `check_portions_unchanged` precedent.
## Item 5 β PR description revert-alignment (via `gh pr edit 527`)
Updated PR #527 body in lock-step with commit 12's revert of
`benches/baseline.json::lint_10kb` top-level fields from {1030,
1033, 1036} (WSL2 dev capture) back to {912, 913, 914} (pre-PR
GHA `ubuntu-latest` capture from PR #498). The CI threshold
`upper_ci_us * 1.10 β 1005Β΅s` aligns with the reverted baseline;
the WSL2 1033Β΅s measurement is preserved in the
`_wsl2_dev_capture` sub-object for cross-host provenance. The
"Baseline refresh (commit 9)" paragraph is replaced with a
"Baseline calibration (commit 9 + commit 12 revert)" paragraph
that explains the calibration arc end-to-end.
Commits table extended with rows 11 (D24 trait-bound relaxation),
12 (R1 items 2-10 + baseline revert + new `noforn-clears-display-
only-to` PageRewrite), 13 (R2 correctness items 1-3), and 14
(this commit). The Decisions section absorbs the post-R2 D22
amendment + D24.
## Item 6 β "Monotone" claim correction
`crates/capco/src/scheme/marking_scheme_impl.rs` previously
described the post-flip pipeline as "monotone". Rewrote to be
precise: the closure operator IS monotone (adds facts only);
PageRewrites are NOT monotone in the FactSet sense β `Clear` and
`FactRemove` are anti-monotone. Pipeline-termination comes from
the topological ordering enforced by the engine's Kahn's
algorithm at `Engine::new` over `PageRewrite::reads` / `writes`,
not from monotonicity. Citation chain (closure monotonicity =
Β§B.3 Table 2 p21 NOFORN-implies axioms; topological scheduler =
lattice-design plan Β§4.7.4).
## Item 7 β `crates/capco/src/scheme/rewrites/mod.rs` rewrite count
Updated module-doc summary from "23 rewrites, in six groups" to
"24 rewrites, in six groups (post-PR-4b-D.2 Copilot R1 #2)"
reflecting the new `capco/noforn-clears-display-only-to` row
from R1 #2.
## Item 8 β `crates/capco/CAPCO-CONTEXT.md` JoinSemilattice paragraph
Rewrote the JoinSemilattice mention to reflect D24 (Copilot R1
#1, 2026-05-18): the `impl JoinSemilattice for CapcoMarking` was
removed; cross-axis CAPCO folds are projections (lossy, multi-
axis-aware), not lattice ops (idempotent, single-axis). Per-axis
lattices (`DissemSet`, `SciSet`, `RelToBlock`, etc.) still
implement `JoinSemilattice`.
## Item 9 β `crates/scheme/src/scope.rs` `DiffInput<M>` doc
The doc comment referenced `M::Marking` (a non-existent
associated type β `M` is itself the `Marking` associated type
on `MarkingScheme`). Corrected to "the scheme's marking type,
`MarkingScheme::Marking`" matching the actual generic parameter
shape.
## Item 10 β `crates/engine/src/engine.rs` `page_marking_arc` comment
Updated the inline comment near the cache build site from
referencing the stale `PageContext::project` to
`project_page_marking(&self.scheme, &page_context)` (which
internally invokes `CapcoScheme::project_from_page_context`).
Reflects the post-hot-path-flip code reality.
## Item 11 β `crates/ism/src/projected.rs` `from_canonical` doc
Rewrote the "Production callers" section with a properly
formatted bullet list (`marque_engine::project_page_marking` +
the bench), added blank lines around the list to satisfy
`clippy::doc_lazy_continuation`, and clarified that
`CapcoScheme::project` itself returns `CapcoMarking`
(`CapcoScheme::Marking`), not `ProjectedMarking` β the bridge to
`ProjectedMarking` is engine-side, not scheme-side.
## Item 12 β `crates/capco/src/scheme/rewrites/noforn_clears.rs`
module doc
Rewrote module-level doc to enumerate the three active NOFORN-
clears rows in declaration order: `capco/noforn-clears-rel-to`,
`capco/noforn-clears-fdr-family`, `capco/noforn-clears-display-
only-to`. Updated the function-level doc on
`noforn_clears_rel_to_rewrite` (the constructor for the family)
to point at the same three rows.
Replaced the misleading `CLOSURE_NOFORN_SAR` example in two
places (line 65 + line 168). The doc cited SAR closure as the
load-bearing trigger for NOFORN injection that drives the
country-axis clearing, but `FDR_DOMINATORS` (`{NOFORN, RELIDO,
DISPLAY_ONLY, AnyInCategory(CAT_REL_TO), EYES}`) suppresses all 7
`CLOSURE_NOFORN_*` rules when DISPLAY ONLY is present in the
input. Replaced with the realistic load-bearing path β the
Pattern-C `capco/dod-ucni-promotes-noforn-when-classified`
PageRewrite per Β§H.6 p116 β which IS reachable from a portion
carrying classified DOD UCNI + DISPLAY ONLY.
## Item 13 β `crates/capco/src/scheme/actions/page_context.rs`
misnomer
The function-level doc on `page_context_to_attrs` claimed the
parity-gate helper `project_via_page_context` was "used by" this
function. The parity-gate helper actually INLINES the
`expected_*` accessor calls directly rather than calling
`page_context_to_attrs`. Reworded to "MIRRORS" with an explicit
note about the inlined accessor calls so a future reader of
either site doesn't get the dependency-direction backwards. Both
sites are retired together at PR 4b-E alongside the PageContext
aggregator.
## Adjacency-walk findings (Item 1 backstop)
Re-confirmed the adjacency walk that backed commit 13's
correctness fix: 8 production `FactAdd.*TOK_NOFORN` call sites,
all benefit from `apply_fact_add`'s self-sufficient Β§H.8 p145
maintenance. Zero production sites currently FactAdd
`RELIDO` / `DISPLAY_ONLY` / `EYES` directly β the inverse-case
rejection (commit 13) is correctness-by-construction for any
future emission path.
## Test verification (toggle-cycle, Item 2 backstop)
Re-ran the delete-rewrite-observe-fail-restore-observe-pass
cycle from commit 13's verification work:
- Commenting out the `apply_fact_add` country-axis clearing
(Item 1 fix in commit 13) causes the 6 new R2 tests in
`crates/capco/tests/category_action_intent.rs` (the
`apply_fact_add_*` cluster) to fail at the direct-caller
path (`scheme.apply_intent(&marking, &intents)` for E021 / E038-
shape calls).
- The integration test
`crates/engine/tests/closure_hotpath.rs::noforn_clears_display_only_to_via_ucni_promote`
still passes when only the apply_fact_add fix is commented
out (the `capco/noforn-clears-display-only-to` PageRewrite
catches the country-axis clearing via the page-rewrite path).
- Commenting out BOTH the apply_fact_add country-axis clearing
AND the new `capco/noforn-clears-display-only-to` PageRewrite
causes the integration test to fail (banner emits
`NOFORN ... DISPLAY ONLY USA, GBR`).
Both layers are load-bearing for their respective call paths.
The PageRewrite layer survives as defense-in-depth per D22.
## Verification gates
- `cargo +stable fmt --all --check`: clean
- `cargo +stable clippy --workspace --tests -- -D warnings`: clean
- `cargo +stable test --workspace`: 0 failures
- `cargo +stable build --workspace`: clean
## Citation re-verification (Constitution VIII)
`Β§B.3 Table 2 p21`, `Β§H.6 p116`, `Β§H.7 p127`, `Β§H.8 p145`,
`Β§H.8 p157`, `Β§G.2 Table 5 p40` re-verified verbatim against
`crates/capco/docs/CAPCO-2016.md` at this commit's authoring
time. No fabrications, no drift, no propagation defects.
## Constitution V Principle V (G13)
Two sentinel panic-format sites switched from debug_assert_eq!'s
`{:?}`-content-leaking format to explicit `if-panic` with
count-only output. Mirrors the existing `check_portions_unchanged`
count-only pattern at `engine.rs:4540-4574`. No document content,
no token canonicals, no country codes, no declassify dates in any
panic output. The grep-target property holds: every
sanctioned content readout site still goes through `expose_secret()`.
Refs: PR #527, Copilot R2 review (15 items: 13 inline + 2
suppressed; commit 13 covers correctness items 1-3, this commit
covers doc items 4-13).
The lint scans `crates/*/src` for CAPCO Β§-citations and flags `Β§I`, `Β§J`, `Β§K` as non-normative (history / examples / acronyms). The reference to Constitution VII Β§IV at marking.rs:58 was parsing as `Β§I` followed by `V` β two defects emitted on the same multi-byte UTF-8 source position. Use `Constitution VII Principle IV` (the form used at `crates/capco/src/rules.rs:3444`) to dodge the regex.
This was referenced May 18, 2026
bashandbone
added a commit
that referenced
this pull request
May 19, 2026
β¦s + baseline correction) Copilot R1 ran on commit 32c33bb (the original R1 fix-ups) and surfaced 5 substantive findings, plus on verification a 6th issue emerged β the original R1 commit's "pre-4b baseline = 15" correction was itself wrong by walked-adjacency-omission. All 6 issues resolved. Ground truth confirmed by walking `git show 5fd5a33:crates/capco/src/scheme/rewrites/*.rs` (the first commit that split rewrites out of monolithic scheme.rs) + counting PageRewrite-row literals in each file at HEAD. 1. **Pin test (Constraint::Custom dedup masking, Copilot R1 #1)** β The test at `post_pr_4b_declares_exact_39_custom_constraints` collected into a BTreeSet before counting; a duplicate `Constraint::Custom("capco/foo", ...)` row would dedupe in the set and the size-only assertion would still pass. Fix: collect into a Vec first, then compare `raw_count == set.len()` β triple-pin (raw count + set size + raw_count_equals_set_size). The equality assertion is the load-bearing dedup check. 2. **CI hollow-coverage (Copilot R1 #2)** β `corpus_parity.rs` is currently `#![cfg(any())]` (disabled by PR 3c.B Commit 10 pending FixProposal-shape rewrite). The `pr-4b-corpus-regression` job mirroring PR 3b's T029 inherited the same dormancy hole. Fix: add three explicit `cargo test --test` invocations for `post_4b_lattice_inventory_pin`, `lattice_static_assertions`, and `post_3b_registration_pin`. With these four active pins plus the still-ACTIVE `corpus_accuracy` and `corpus_provenance` suites, the job has real 4b-specific coverage even while corpus_parity stays dormant. Inline comments clarify which suites are ACTIVE vs dormant so a future reader who sees the `corpus_parity` invocation passing doesn't conclude it gives real coverage. 3. **Attestation `DeclassExemptionAccumulator` citation (Copilot R1 #3)** β Walked-adjacency miss from the R1 fix-up on `DeclassifyOnLattice`. Same root cause: Β§H.6 grounds the AEA *exception*, not the general declassification-exemption hierarchy rule. Fix: Β§H.6 β Β§E.3 pp32-33 (Multiple Sources) + Β§E.1 p31 (exemption catalog 25X#/50X#/75X#), matching the doc-comment Β§-authority block at the type definition. 4. **Attestation `Pre-4b baseline` cell (Copilot R1 #4)** β The table at "Per-axis net-delta math" had "~14 (pre-Pattern-B/C)" with the tilde flagging an unverified approximate. Fix: exact baseline = 14 (4 pattern_a + 2 noforn_clears + 8 transmutation_stubs). The 3rd noforn_clears row (`noforn-clears-display-only-to`) was added by PR 4b-D.2 (#527), not pre-4b. 5. **Pin doc-comment derivation chain (Copilot R1 #5)** β The header doc-comment said "Pattern C 8 rows" in the 4b-C delta, but 4b-C landed only 7 Pattern-C rows; the 8th (`sbu-nf-evicted-by-classified`) was added later by #541 in the 4b-F window. Fix: change "Pattern C 8 rows" β "Pattern C 7 rows" in the 4b-C section; add the #541 +1 entry separately in the 4b-F section; document the 4b-D.2 +1 PageRewrite contribution that the original doc-comment also omitted. 6. **R1 baseline correction (walked-adjacency from #4)** β The original R1 fix-up commit (32c33bb) changed `CLAUDE.md:283` from "14 β 27" to "15 β 27" based on the architect plan's "15 = 4 pattern_a + 3 noforn_clears + 8 transmutation_stubs" claim. The architect's count of 3 noforn_clears rows was off-by-one: pre-4b had only 2 (`noforn-clears-rel-to` + `noforn-clears-fdr-family`); the 3rd row (`noforn-clears-display-only-to`) was added by 4b-D.2 (#527). Walked the git history at `5fd5a339` (Stage 2 PR A sub-split of `scheme/rewrites/`) and counted the literal rows: pattern_a 4 / noforn_clears 2 / transmutation_stubs 8 = 14 pre-4b. The original 4b-C landing CLAUDE.md entry's "catalog row count 14 β 23" is ground truth; my R1 fix-up over-corrected to 15 β 27 and needs the inverse correction back to 14 β 27 with the full per-sub-PR breakdown: Pre-4b: 14 (4 pattern_a + 2 noforn_clears + 8 transmutation_stubs) 4b-C +9: 23 (Pattern-B 2 + Pattern-C 7) 4b-D.2 +1: 24 (noforn-clears-display-only-to per Β§H.8 p145) #541 +1: 25 (sbu-nf-evicted-by-classified Pattern-C row 2b per Β§H.9 p178) #552 +1: 26 (sbu-nf-supersedes-sbu per Β§H.9 p178) #555 +1: 27 (les-nf-supersedes-les per Β§H.9 p185) 13 new rows over the 4b umbrella + post-4b-F window. 14 + 13 = 27 β (matches the test's hardcoded expected count). Verification gate (all GREEN): - `cargo +stable test -p marque-capco --test post_4b_lattice_inventory_pin` β 3/3 PASS (including new raw_count_equals_set_size assertion) - `cargo +stable test -p marque-capco --test lattice_static_assertions` β 0/0 (compile-time only, file builds clean) - `cargo +stable test -p marque-capco --test post_3b_registration_pin` β 1/1 PASS (38 unchanged) - `cargo +stable clippy --workspace --all-targets -- -D warnings` β 0 warnings - `cargo +stable fmt --check` β clean - `cargo run --manifest-path tools/citation-lint/Cargo.toml --release -- .` β 0 defects Per project memory `feedback_audit_predicates_against_source` (Constitution VIII propagation discipline) and `feedback_double_check_the_plan` (when the architect plan and ground truth conflict, ground truth wins; the architect plan was a preflight draft, the merged code is authoritative). Per project memory `feedback_walked_adjacencies` (every fix walks the related code paths) β the DeclassExemptionAccumulator fix #3 is a textbook walked-adjacency from the R1 DeclassifyOnLattice fix that I missed the first time; the baseline correction #6 is a walked-adjacency from the architect plan's count that I trusted instead of verifying.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The hot-path flip of the marque engine refactor (spec 006). Three production paths move from PageContext-aggregation to lattice-aggregation + closure + page_rewrites:
JoinSemilattice::joinforCapcoMarking(crates/capco/src/scheme/marking.rs)MarkingScheme::projectforCapcoScheme(crates/capco/src/scheme/marking_scheme_impl.rs)Engine::lintanddispatch_page_finalization(crates/engine/src/engine.rs)Pipeline ordering per lattice-design plan Β§4.7.4: parse β join β closure β page_rewrites β render.
PageContext stays alive feeding
RuleContext::page_contextfor banner-validation rules; deletion is the separate PR 4b-E. Consumer migration ofctx.page_context.expected_*βctx.page_markingis the separate PR 4b-D.3.This PR also activates
CapcoScheme::closure()(from PR #517 / 4b-D.1) on the production hot path, with the closure-rewrite-application sentinel from lattice-design Β§3 (e.1).Engine-crate touch authorization (Constitution VII Β§IV)
This PR edits
crates/engine/src/engine.rsdespite being scheme-adoption work. Authorization derives from the within-006 precedent:expected_dissem_us+ RELIDO observed-unanimity at banner roll-upThe engine edits here (commits 4, 7, 8) are the same bugfix class: rerouting two call sites (
page_marking_arc.get_or_insert_withanddispatch_page_finalization's mirror) frompage_context.project()toproject_page_marking(&self.scheme, &page_context). No new engine behavior, no new API surface onEngine, no pass-structure changes, no audit-log changes.Commits
eac128eefeat(ism): add ProjectedMarking::from_canonical constructor54c57b92feat(capco): flip JoinSemilattice::join to lattice path6d7eccfefeat(capco): wire CapcoScheme::project to lattice + closure + page_rewrites75f23dc3feat(engine): drive page_marking through scheme.project on the hot pathc906d92ftest(capco,engine): Pattern D lattice fixtures + parity retargets + doc sync671aa560perf(capco): short-circuit CapcoScheme::closure on empty cone triggers6cdaf132perf(capco,engine): engine-side fast-path for CapcoScheme::project5ea386baperf(capco,engine): borrow PageContext through the projection pipeline58a46c67perf(bench): refresh lint_10kb baseline to PR 4b-D.2 post-perf state(superseded by R1 #3 β see commit 12)d33e14e1docs,refactor: address reviewer feedback (4b-D.2)ef3d25farefactor(scheme,capco): drop impl JoinSemilattice for CapcoMarking; relax MarkingScheme bound (Copilot R1 #1 / D24)b8644ebafix,docs: address Copilot R1 items 2-10(incl. baseline revert to GHA 912/913/914, newcapco/noforn-clears-display-only-toPageRewrite +CAT_DISPLAY_ONLY_TO)d57d1e51fix(capco): complete NOFORN supersession at apply_fact_add injection site (Copilot R2 suppressed #2 + inverse case)docs,fix: address Copilot R2 nits + G13 sentinel content-ignoranceNet: ~+TBD across ~20 files.
Decisions captured in
specs/006-engine-rule-refactor/decisions.mdapply_fact_addmaintains Β§H.8 p145 across all three axes when NOFORN is inserted into CAT_DISSEM:DissemSet::with_noforn_injectedstrips dominatedRel/Relido/Displayonly/Eyestokens fromdissem_us.attrs.rel_toandattrs.display_only_tocountry lists so the renderer cannot emitNOFORN ... REL TO USA, GBR.Rel/Relido/Displayonly/Eyesonto a marking already carrying NOFORN returnsApplyIntentError::IntentInapplicable, preventing the renderer from ever seeing the Β§H.8 p145 violation regardless of FactAdd order.All three axes hold at the apply_fact_add injection site; the PageRewrite layer (
capco/noforn-clears-{rel-to,fdr-family,display-only-to}) survives as defense-in-depth for any future emission path that bypassesapply_intent.CapcoScheme::projectbetweenjoin_via_latticeandclosure());#[cfg(debug_assertions)]snapshot+assert pattern; sibling to the engine's PageFinalization rule-dispatch sentinel. Catches any future refactor that mutates per-portionCanonicalAttrsduring closure (violates lattice-design Β§3 (e.1) read-only-attrs invariant). G13 content-ignorance update (commit 14): the snapshot-vs-current divergence panic emits portion counts only, neverCanonicalAttrscontent β mirrors thecheck_portions_unchangedcount-only pattern atengine.rs:4540-4574.impl JoinSemilattice for CapcoMarking; relaxMarkingScheme::Markingbound (commit 11, Copilot R1 feat: Phase 1 setup β marque-ism crate, dev scaffolding, and review fixesΒ #1): cross-axis CAPCO folds are projections (lossy, multi-axis-aware), not lattice operations (idempotent, single-axis). Keeping a meaningless.joinonCapcoMarkingwould invite misuse. The bound onMarkingScheme::Markingrelaxes fromJoinSemilatticetoSized + Clone + Send + Sync; per-axis lattices (DissemSet,SciSet,RelToBlock, etc.) still implementJoinSemilattice.DiffInput<M>bound dropped in lock-step.Performance
Bench-measured tier ladder on WSL2 dev worktree:
lint_10kbmedianubuntu-latest, PR #498)Lands in the +20% acceptable tier authorized by the project's perf-baseline policy. GHA
ubuntu-latesttypically reads 5-10% faster than WSL2; expected GHA capture is in the +10% noise band of the original 913Β΅s baseline.Baseline calibration (commit 9 + commit 12 revert): the perf-mitigation work captured 1033Β΅s on WSL2. Commit 9 (
58a46c67) initially refreshedbenches/baseline.json::lint_10kbto {1030, 1033, 1036}; Copilot R1 #3 flagged that the CI regression gate consumes the top-level fields and must be calibrated to the CI hardware (GHAubuntu-latest), not WSL2 (5-10% slower). Commit 12 (b8644eba) reverted the top-level fields back to the pre-PR GHA baseline {912, 913, 914}Β΅s (CI thresholdupper_ci_us * 1.10 β 1005Β΅s); the WSL2 capture is preserved in thelint_10kb._wsl2_dev_capturesub-object for cross-host provenance. The first GHAubuntu-latestre-capture of this branch viascripts/capture-baselines.shafter merge produces the post-flip authoritative numbers.Parity gate
crates/capco/tests/page_context_lattice_parity.rs: 74/74 tests pass._pending_pr_4b_dto post-flip names:fouo_classified_scheme_project_strips_fouo(Β§H.8 p134 FOUO Precedence βcapco/classification-evicts-fouopage rewrite fires)aea_ucni_classified_scheme_project_strips_and_promotes_noforn(Β§H.6 p116 + p118 DOD/DOE UCNI βcapco/dod-ucni-evicted-by-classified+capco/dod-ucni-promotes-noforn-when-classifiedrewrites fire)pure_nato_lattice_vs_pagecontext_diverges(Β§H.7 pp123-125 β solely-NATO pages preserveNato(_)on the lattice path; PageContext flattens toUs(_))joint_unanimous_two_portions(Β§H.3 p56 β pure-JOINT pages preserveJoint(_))joint_single_portion_no_us(Β§H.3 p56 β solo-JOINT portions preserveJoint(_))relido_plus_nf_noforn_dominates_parityremoved from the divergence inventory (already converged in staging; asserts byte-identity)Direction inversion: post-flip the parity gate's source of truth flips.
project_via_scheme(production) now agrees withproject_via_lattice;project_via_page_contextbecomes the divergent side until PR 4b-D.3 / 4b-E migrate consumers and delete PageContext.Closure runtime activation
CapcoScheme::closure()(landed in PR #517 / 4b-D.1, test-reachable only) is now wired on the production hot path. All 8 closure rules fire throughscheme.project(Scope::Page, ...):CLOSURE_NOFORN_*rules (SAR / AEA_RD / UCNI / FGI / ORCON / RSEN_IMCON_DSEN / NONICCONTROLS) per Β§B.3 Table 2 p21CLOSURE_REL_TO_USA_NATOrule per Β§H.7 p127 + Β§G.2 Table 5 p40 (example-derived; D20 layer-separation: Severity::Info silent at lattice layer; S007 at Severity::Suggest visible at text layer)FDR_DOMINATORSsuppressor ={NOFORN, RELIDO, DISPLAY_ONLY, AnyInCategory(CAT_REL_TO), EYES}matches Β§B.3.a p19 canonical FD&R enumeration. EYES inclusion correct per Β§H.8 p157.Tests
cargo +stable test --workspace)crates/capco/tests/page_context_lattice_parity.rscrates/capco/tests/closure_runtime.rscrates/capco/tests/category_action_intent.rscrates/engine/tests/closure_hotpath.rs(new)crates/engine/tests/lattice_corpus.rs(new)crates/ism/src/projected.rs(from_canonicalunit tests)cargo +stable clippy --workspace --tests -- -D warningsConstitution VIII citation verification
The CAPCO dissem reviewer re-verified 10 Β§-citations verbatim against
crates/capco/docs/CAPCO-2016.md:No fabrications, no drift, no propagation defects.
CAPCO-CONTEXT.md Β§3 sync
crates/capco/CAPCO-CONTEXT.mdΒ§3 updated:relido_plus_nf_noforn_dominates_parityremoved (already converged)Out of scope (tracked separately)
classified_us,project_page,rel_to_contains,dissem_containsduplicated acrossclosure_hotpath.rs+lattice_corpus.rs; deferred to a follow-up test-only PR that addscrates/engine/tests/common.rsCLOSURE_NOFORN_UCNIDoD coverage gap:TOK_DCNInot in trigger list; pre-existing per issue Expand Vocabulary sentinel set + add NNPI + bare-form rewrite fixers (CNWDI / SI-NK / SI-EU)Β #407. The Β§H.6 p116 NOFORN-promotion semantic IS satisfied on the production path via thecapco/dod-ucni-promotes-noforn-when-classifiedpage rewrite (PR 4b-C). Closure-layer coverage tracked separately.page_context_to_attrsretirement: lone production consumer retired with this PR; function survives behind#[allow(dead_code)]pending PR 4b-E PageContext deletionPR 4b-D sequence
CapcoScheme::closure()Kleene-fixpoint override (test-reachable)ctx.page_context.expected_*consumers βctx.page_markingTest plan
cargo +stable build --workspacecleancargo +stable clippy --workspace --tests -- -D warningscleancargo +stable fmt --all --checkcleancargo +stable test --workspaceall green (0 failures)cargo +stable test -p marque-capco --test page_context_lattice_parity74/74cargo +stable test -p marque-capco --test category_action_intentincludes 6 new R2-coverage testscargo +stable test -p marque-engine --test closure_hotpath13/13 (UCNI fixture retargeted per R2 Add Claude Code GitHub WorkflowΒ #2)cargo +stable test -p marque-engine --test lattice_corpus4/4cargo bench --bench lint_latency -- lint_10kbmedian 1033Β΅s (WSL2) within +20% acceptable tier; top-level baseline reverted to GHA {912, 913, 914} (CI threshold ~1005Β΅s)