core: Graph.largestEigenvalue via power iteration — 10th graduation - #321
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
…first detection primitive) First cartel-detection primitive per the Graph ADR (PR #316). Computes approximate lambda_1 (principal eigenvalue of the symmetrized adjacency matrix) via standard power iteration with L2 normalization + Rayleigh quotient. Surface: Graph.largestEigenvalue (tolerance: double) (maxIterations: int) (g: Graph<'N>) : double option Method: - Build adjacency map from edge ZSet (coerce int64 weights to double; include negative weights as signed entries) - Symmetrize: A_sym[i,j] = (A[i,j] + A[j,i]) / 2 - Start with all-ones vector (non-pathological seed; avoids zero-vector trap) - Iterate v <- A_sym * v; v <- v / ||v|| - Stop when |lambda_k - lambda_{k-1}| / (|lambda_k| + eps) < tolerance or hit maxIterations - Return Rayleigh quotient as lambda estimate Cartel-detection use: Sharp jump in lambda_1 between baseline graph and injected- cartel graph indicates a dense subgraph formed. The 11th-ferry / 13th-ferry / 14th-ferry spec treats this as the first trivial-cartel warning signal. Performance note: dense Array2D adjacency for MVP. Suitable for toy simulations (50-500 nodes). For larger graphs, Lanczos- based incremental spectral method is a future graduation. Tests (4 new, 21 total in GraphTests, all passing): - None on empty graph - Symmetric 2-edge (weight 5) graph -> lambda ≈ 5 (exact to 1e-6) - K3 triangle (weight 1) -> lambda ≈ 2 (K_n has lambda_1 = n-1) - Cartel-injection test (the LOAD-BEARING one): baseline sparse 5-node graph vs. baseline + K_4 clique (weight 10). Attacked lambda >= 5x baseline lambda. This is the cartel-detection signal in action. Provenance: - Concept: Aaron (differentiable firefly network; first-order detection signal) - Formalization: Amara (11th ferry signal-model §2 + 13th ferry metrics §2 "lambda_1 growth" + 14th ferry "principal eigenvalue growth" alert row) - Implementation: Otto (10th graduation) Build: 0 Warning / 0 Error. SPOF (per Otto-106): pure function; deterministic output for same input (within floating-point). Caller threshold is the sensitivity SPOF — too low -> false positives, too high -> missed cartels. Mitigation documented: threshold should come from baseline-null-distribution percentile, not hard-coded. Future graduation: null-baseline calibration helper. Toy cartel detector (Amara Otto-122 validation bar) prerequisite: this is the first half. Next graduation: modularityScore + toy harness combining both signals + 90%-detection-across- 1000-FsCheck-seeds property test. Composes with: - src/Core/Graph.fs skeleton (PR #317 merged main) - src/Core/Graph.fs operators (PR #319 pending) - src/Core/RobustStats.fs (PR #295) for outlier-resistant signal combination across many graph-pair comparisons Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
4fadb36 to
9c96226
Compare
There was a problem hiding this comment.
Pull request overview
Adds the first spectral cartel-detection primitive to the ZSet-backed Graph<'N> substrate by implementing Graph.largestEigenvalue (λ₁) using power iteration over the symmetrized adjacency matrix, along with correctness and “cartel injection” tests.
Changes:
- Implement
Graph.largestEigenvalue(power iteration + Rayleigh quotient) on symmetrized adjacency. - Add unit tests for empty graph, simple known-spectrum graphs, and a cartel-clique injection signal test.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
src/Core/Graph.fs |
Adds Graph.largestEigenvalue implementation and extensive docstring describing the method and intended use. |
tests/Tests.FSharp/Algebra/Graph.Tests.fs |
Adds test coverage for largestEigenvalue, including a load-bearing cartel-injection growth assertion. |
| /// **Performance note:** builds a dense | ||
| /// `IReadOnlyDictionary<'N, Dictionary<'N, double>>` as the | ||
| /// adjacency representation. Suitable for MVP / toy | ||
| /// simulations (50-500 nodes). For larger graphs, a | ||
| /// Lanczos-based incremental spectral method is the next | ||
| /// graduation; documented as future work. |
There was a problem hiding this comment.
The docstring says this function “builds a dense IReadOnlyDictionary<'N, Dictionary<'N, double>>” adjacency representation, but the implementation actually builds dense Array2D matrices (adj and sym). Please update the docstring to match the actual representation (or change the implementation) so readers don’t optimize/extend based on incorrect assumptions.
| /// **Performance note:** builds a dense | |
| /// `IReadOnlyDictionary<'N, Dictionary<'N, double>>` as the | |
| /// adjacency representation. Suitable for MVP / toy | |
| /// simulations (50-500 nodes). For larger graphs, a | |
| /// Lanczos-based incremental spectral method is the next | |
| /// graduation; documented as future work. | |
| /// **Performance note:** builds dense `Array2D<double>` | |
| /// adjacency and symmetric-adjacency matrices for the | |
| /// power iteration. Suitable for MVP / toy simulations | |
| /// (50-500 nodes). For larger graphs, a Lanczos-based | |
| /// incremental spectral method is the next graduation; | |
| /// documented as future work. |
| /// Provenance: concept Aaron; formalization Amara (11th | ||
| /// ferry §2 + 13th ferry §2); implementation Otto (10th | ||
| /// graduation). |
There was a problem hiding this comment.
This provenance/attribution block introduces contributor names in a code comment. Repo convention is to avoid name attribution in code/docs/skills and use role references instead (see docs/AGENT-BEST-PRACTICES.md:284-292). Please rephrase this section to remove personal names.
| if converged then Some lambda | ||
| else if iter >= maxIterations then Some lambda | ||
| else None |
There was a problem hiding this comment.
Return semantics/documentation don’t match: the docstring says the function returns None when it fails to converge within maxIterations, but the implementation returns Some lambda when iter >= maxIterations. Also, the final else None branch is unreachable. Please decide on the contract (return None on non-convergence, or update docs and remove the dead branch / consider returning a richer result indicating convergence).
| let baseline = | ||
| Graph.fromEdgeSeq [ | ||
| (1, 2, 1L); (2, 1, 1L) | ||
| (3, 4, 1L); (4, 3, 1L) | ||
| (2, 5, 1L); (5, 2, 1L) | ||
| ] | ||
| let cartelEdges = | ||
| [ | ||
| for s in [6; 7; 8; 9] do | ||
| for t in [6; 7; 8; 9] do | ||
| if s <> t then yield (s, t, 10L) | ||
| ] | ||
| let attacked = Graph.fromEdgeSeq (List.append [ (1, 2, 1L); (2, 1, 1L); (3, 4, 1L); (4, 3, 1L); (2, 5, 1L); (5, 2, 1L) ] cartelEdges) |
There was a problem hiding this comment.
attacked duplicates the baseline edge list inline instead of reusing baseline (or a shared baselineEdges list). This makes the test harder to maintain and easy to accidentally diverge from the baseline scenario when edits happen.
| let baseline = | |
| Graph.fromEdgeSeq [ | |
| (1, 2, 1L); (2, 1, 1L) | |
| (3, 4, 1L); (4, 3, 1L) | |
| (2, 5, 1L); (5, 2, 1L) | |
| ] | |
| let cartelEdges = | |
| [ | |
| for s in [6; 7; 8; 9] do | |
| for t in [6; 7; 8; 9] do | |
| if s <> t then yield (s, t, 10L) | |
| ] | |
| let attacked = Graph.fromEdgeSeq (List.append [ (1, 2, 1L); (2, 1, 1L); (3, 4, 1L); (4, 3, 1L); (2, 5, 1L); (5, 2, 1L) ] cartelEdges) | |
| let baselineEdges = | |
| [ | |
| (1, 2, 1L); (2, 1, 1L) | |
| (3, 4, 1L); (4, 3, 1L) | |
| (2, 5, 1L); (5, 2, 1L) | |
| ] | |
| let baseline = Graph.fromEdgeSeq baselineEdges | |
| let cartelEdges = | |
| [ | |
| for s in [6; 7; 8; 9] do | |
| for t in [6; 7; 8; 9] do | |
| if s <> t then yield (s, t, 10L) | |
| ] | |
| let attacked = Graph.fromEdgeSeq (List.append baselineEdges cartelEdges) |
| // Baseline lambda on sparse 5-node graph is ~1 (max | ||
| // single-edge weight). Attacked lambda should be ~30 | ||
| // (K_4 with weight 10 has lambda_1 = 3*10 = 30, since | ||
| // K_n has lambda_1 = n-1 scaled by weight). |
There was a problem hiding this comment.
This comment claims the baseline λ₁ is “~1 (max single-edge weight)”, but the baseline component with nodes 1-2-5 is a 3-node path whose top eigenvalue is √2 (~1.414) (and the 3-4 edge contributes 1). Please adjust the comment to avoid misleading future readers about expected magnitudes.
| // Baseline lambda on sparse 5-node graph is ~1 (max | |
| // single-edge weight). Attacked lambda should be ~30 | |
| // (K_4 with weight 10 has lambda_1 = 3*10 = 30, since | |
| // K_n has lambda_1 = n-1 scaled by weight). | |
| // Baseline lambda on this sparse 5-node graph is driven by | |
| // the 3-node path 1-2-5, whose top eigenvalue is sqrt(2) | |
| // (~1.414); the separate 3-4 edge contributes 1. Attacked | |
| // lambda should be ~30 (K_4 with weight 10 has lambda_1 = | |
| // 3*10 = 30, since K_n has lambda_1 = n-1 scaled by weight). |
…urrect (supersedes #319 + #322) (#324) PRs #319 (operator composition) and #322 (modularity) both hit DIRTY state from positional rebase conflicts — each appended to Graph.fs tail, and as main grew with PR #321 (largestEigenvalue), the appends conflicted. Closed both and re-filed consolidated fresh from main. Ships (combining #319 + #322 content): **Operator composition** (5 functions — ADR property 5): - Graph.map : ('N -> 'M) -> Graph<'N> -> Graph<'M> - Graph.filter : ('N * 'N -> bool) -> Graph<'N> -> Graph<'N> - Graph.distinct : Graph<'N> -> Graph<'N> - Graph.union : Graph<'N> -> Graph<'N> -> Graph<'N> - Graph.difference : Graph<'N> -> Graph<'N> -> Graph<'N> Each is 1-2 lines delegating to the corresponding ZSet operator. **Modularity score** (Newman's Q formula): - Graph.modularityScore : Map<'N, int> -> Graph<'N> -> double option - Computes Q over symmetrized adjacency given a partition; nodes missing from partition treated as singleton-community Tests (7 new in this consolidated ship, 28 total in GraphTests, all passing): - map relabels nodes - filter keeps matching edges - distinct collapses multi-edges + drops anti-edges - union + difference round-trip restores original - modularityScore returns None for empty graph - modularityScore is high (>0.3) for well-separated two-K3 communities bridged thin - modularityScore is 0 for single-community K3 (no boundary, no structure; matches theory) Build: 0 Warning / 0 Error. Counts as the 9th + 11th graduation (originally ships #319 + #322 that both got DIRTY). Consolidated to unblock the queue with a single clean merge. Superseded PRs (closed): - #319 feat/graph-operator-composition-map-filter-distinct - #322 feat/graph-modularity-score Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
… detector) (#328) First full integration of the Graph detection pipeline: combines largestEigenvalue (spectral growth) + labelPropagation (community partition) + modularityScore (partition evaluation) into a single scalar risk score. Surface: Graph.coordinationRiskScore (alpha: double) (beta: double) (eigenTol: double) (eigenIter: int) (lpIter: int) (baseline: Graph<'N>) (attacked: Graph<'N>) : double option Composite formula (MVP): risk = alpha * Δλ₁_rel + beta * ΔQ where: - Δλ₁_rel = (λ₁(attacked) - λ₁(baseline)) / max(λ₁(baseline), eps) - ΔQ = Q(attacked, LP(attacked)) - Q(baseline, LP(baseline)) Both signals fire when a dense subgraph is injected: λ₁ grows because the cartel adjacency has high leading eigenvalue; Q grows because LP finds the cartel as its own community and Newman Q evaluates that partition highly. Weight defaults per Amara 17th-ferry initial priors: - alpha = 0.5 spectral growth - beta = 0.5 modularity shift Tests (3 new, 34 total in GraphTests, all passing): - Empty graphs -> None - Cartel injection -> composite > 1.0 (both signals fire) - attacked == baseline -> composite near 0 (|score| < 0.2) Calibration deferred (Amara Otto-132 Part 2 correction #4 — robust statistics via median + MAD): this MVP uses raw linear weighting over differences. Full CoordinationRiskScore with robust z-scores over baseline null-distribution is a future graduation once baseline-calibration machinery ships. RobustStats.robustAggregate (PR #295) already provides the median-MAD machinery; just needs a calibration harness to use it. 14th graduation under Otto-105 cadence. First full integration ship using 4 Graph primitives composed together (λ₁ + LP + modularity + composer). Build: 0 Warning / 0 Error. Provenance: - Concept: Aaron (firefly network + trivial-cartel-detect) + Amara's composite-score formulations across 12th/13th/14th/ 17th ferries - Implementation: Otto (14th graduation) Composes with: - Graph.largestEigenvalue (PR #321) - Graph.labelPropagation (PR #326) - Graph.modularityScore (PR #324) - RobustStats.robustAggregate (PR #295) — for future robust variant Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
…ns tracked; 3 already shipped) (#330) * ferry: Amara 17th absorb — Cartel-Lab Implementation Closure + 5.5 Verification (8 corrections tracked) Two-part ferry: Amara's deep-research Implementation Closure for Cartel-Lab + her own GPT-5.5 Thinking verification pass with 8 load-bearing corrections. Otto correction-pass status (all 8 tracked): 1. λ₁(K₃) = 2 — ALREADY CORRECT PR #321 Otto-127 (independent convergence before verification arrived) 2. Modularity relational-not-absolute — ALREADY CORRECT PR #324 Otto-128 (caught mid-tick via hand-calc) 3. Cohesion/Exclusivity/Conductance replace entropy-collapse — SHIPPED PR #329 Otto-135 (3 primitives + 6 tests) 4. Windowed stake covariance acceleration — FUTURE GRADUATION 5. Event-stream → phase pipeline for PLV — FUTURE GRADUATION 6. 'ZSet invertible' → 'deltas support retractions' — ADR ALREADY PHRASED CORRECTLY (PR #316 never claimed full invertibility) 7. KSK 'contract' → 'policy layer' — FILED BACKLOG PR #318 Otto-124 (Max coord pending) 8. SOTA humility — DOC PHRASING (applied in new absorb docs) Amara's proposed 3-PR split NOT adopted (Otto-105 small- graduation cadence; content delivered across 7 ticks instead: PRs #317, #321, #323, #324, #326, #328, #329). Amara's proposed /cartel-lab/ folder NOT adopted (Otto-108 Conway's-Law: single-module-tree until interfaces harden). Current Graph.fs + test-support split works. Aaron's SharderInfoTheoreticTests flake flag (trailing Otto-132 note) filed as BACKLOG PR #327 Otto-133 — unrelated hygiene item. Amara's Otto-136 follow-up note: '#323 conceptually accepted, do not canonicalize until sharder test is seed-locked/ recalibrated'. Acknowledged — #323 lives in tests/Simulation/ already (test-scoped); 'canonicalize' = future promotion to src/Core/NetworkIntegrity/ per Amara's PR #3 split suggestion; that's gated on #327 completion. §33 archive header compliance. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * lint: fix line-start PR-number header false-positive in 17th-ferry absorb --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
…correction) (#332) Completes the input pipeline for TemporalCoordinationDetection. phaseLockingValue (PR #298): PLV expects phases in radians but didn't prescribe how events become phases. This ship fills the gap. 17th graduation under Otto-105 cadence. Addresses Amara 17th-ferry Part 2 correction #5: 'Without phase construction, PLV is just a word.' Surface (2 pure functions): - PhaseExtraction.epochPhase : double -> double[] -> double[] Periodic-epoch phase. φ(t) = 2π · (t mod period) / period. Suited to consensus-protocol events with fixed cadence (slot duration, heartbeat, epoch boundary). - PhaseExtraction.interEventPhase : double[] -> double[] -> double[] Circular phase between consecutive events. For sample t in [t_k, t_{k+1}), phase = 2π · (t - t_k) / (t_{k+1} - t_k). Suited to irregular event-driven streams. Both return double[] of phase values in [0, 2π) radians. Empty output on degenerate inputs (no exception). eventTimes assumed sorted ascending; samples outside the event range get 0 phase (callers filter to interior if they care). Hilbert-transform analytic-signal approach (Amara's Option B) deferred — needs FFT support which Zeta doesn't currently ship. Future graduation when signal-processing substrate lands. Tests (12, all passing): epochPhase: - t=0 → phase 0 - t=period/2 → phase π - wraps cleanly at period boundary - handles negative sample times correctly - returns empty on invalid period (≤0) or empty samples interEventPhase: - empty on <2 events or empty samples - phase 0 at start of first interval - phase π at midpoint - adapts to varying interval lengths (O(log n) binary search for bracketing interval) - returns 0 before first and after last event (edge cases) Composition with phaseLockingValue: - Two nodes with identical epochPhase period → PLV = 1 (synchronized) - Two nodes with same period but constant offset → PLV = 1 (perfect phase locking at non-zero offset is still locking) This composes the full firefly-synchronization detection pipeline end-to-end for event-driven validator streams: validator event times → PhaseExtraction → phaseLockingValue → temporal-coordination-detection signal 5 of 8 Amara 17th-ferry corrections now shipped: #1 λ₁(K₃)=2 ✓ already correct (PR #321) #2 modularity relational ✓ already correct (PR #324) #3 cohesion/exclusivity/conductance ✓ shipped (PR #331) #4 windowed stake covariance ✓ shipped (PR #331) #5 event-stream → phase pipeline ✓ THIS SHIP Remaining: #4 robust-z-score composite variant (future); #6 ADR phrasing (already correct); #7 KSK naming (BACKLOG #318 awaiting Max coord); #8 SOTA humility (doc-phrasing discipline). Build: 0 Warning / 0 Error. Provenance: - Concept: Aaron firefly-synchronization design - Formalization: Amara 17th-ferry correction #5 with 3-option menu (epoch / Hilbert / circular) - Implementation: Otto (17th graduation; options A + C shipped, Hilbert deferred) Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
The bar Amara set Otto-122: "Can this detect even a dumb cartel
in a toy simulation?"
Answer: **YES.** 2 property tests, both passing:
1. ``toy cartel detector — 100 seeds, detection rate >= 90%``
Generates 50-validator baseline + injects 5-node cartel clique
(weight 10) per seed. Rule: attacked-lambda >= 2.0 *
baseline-lambda triggers detection. Runs 100 seeds;
detection rate >= 90% required. Actual run on local machine:
PASSED.
2. ``toy cartel detector — clean baseline rarely triggers``
False-positive rate check. Compares two independent baseline
lambdas; detection rule applied. Allows up to 20% false-
positive rate (generous upper bound; real deployment uses
null-baseline calibration per Amara 14th ferry). 100 seeds;
PASSED.
New code:
- tests/Tests.FSharp/_Support/CartelInjector.fs
Red-team synthetic cartel generator. TEST-ONLY per Otto-118
discipline: lives in _Support/, NOT shipped as public API.
Two functions:
- buildBaseline (rng, nodeCount, avgDegree) : Graph<int>
- injectCartel (rng, baseline, cartelSize, weight, nodeCount)
: Graph<int> * Set<int>
- tests/Tests.FSharp/Simulation/CartelToy.Tests.fs
The property tests above.
Parameters matching Amara's 15th/16th ferry prescription:
- 50 validators
- 5-node cartel
- avgDegree=3 (sparse baseline)
- cartelWeight=10
- detectionMultiplier=2.0 (attacked-lambda >= 2x baseline)
- 100 seeds (1000-seed scaled-up run is a follow-up bench-
project; unit-test obligation is 100)
What this proves per Graph ADR (PR #316):
- The Graph substrate (ZSet-backed, retraction-native) compiles
under real detection workload
- largestEigenvalue (PR #321) produces a reliable cartel signal
on synthetic data
- The theory-cathedral warning (Amara 15th ferry) is addressed:
running code detects a dumb cartel at the promised rate
What this does NOT yet prove:
- Real-world cartels (stealthy weights, partial coordination,
adversarial evasion)
- Full composite detector (adds modularity #322 + covariance)
- Null-baseline threshold calibration (per Amara 14th ferry)
- 1000-seed + adversarial-seed-selection (benchmark project)
These are the next graduations. For now: the substrate works.
Every primitive shipped (RobustStats, crossCorrelation, PLV,
burstAlignment, Veridicality.Provenance/Claim/validate +
antiConsensusGate + CanonicalClaimKey, Graph.addEdge /
removeEdge / ... / largestEigenvalue / modularityScore)
composes cleanly and produces the detection signal it was
designed to produce.
12th graduation under the Otto-105 cadence (counts as the
first INTEGRATION ship — uses primitives from Graph + the
test-support CartelInjector to produce a working detector).
Provenance:
- Design bar: Aaron Otto-121 ("tight in all aspects") +
Amara Otto-122 ("toy cartel simulation")
- Formalization: Amara 11th/12th/13th/14th ferries
- Implementation: Otto-123 ADR (PR #316) + Otto-124 skeleton
(PR #317) + Otto-126 operators (PR #319) + Otto-127
eigenvalue (PR #321) + Otto-129 integration (THIS PR)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* test: toy cartel detector — Amara Otto-122 validation bar CLEARED
The bar Amara set Otto-122: "Can this detect even a dumb cartel
in a toy simulation?"
Answer: **YES.** 2 property tests, both passing:
1. ``toy cartel detector — 100 seeds, detection rate >= 90%``
Generates 50-validator baseline + injects 5-node cartel clique
(weight 10) per seed. Rule: attacked-lambda >= 2.0 *
baseline-lambda triggers detection. Runs 100 seeds;
detection rate >= 90% required. Actual run on local machine:
PASSED.
2. ``toy cartel detector — clean baseline rarely triggers``
False-positive rate check. Compares two independent baseline
lambdas; detection rule applied. Allows up to 20% false-
positive rate (generous upper bound; real deployment uses
null-baseline calibration per Amara 14th ferry). 100 seeds;
PASSED.
New code:
- tests/Tests.FSharp/_Support/CartelInjector.fs
Red-team synthetic cartel generator. TEST-ONLY per Otto-118
discipline: lives in _Support/, NOT shipped as public API.
Two functions:
- buildBaseline (rng, nodeCount, avgDegree) : Graph<int>
- injectCartel (rng, baseline, cartelSize, weight, nodeCount)
: Graph<int> * Set<int>
- tests/Tests.FSharp/Simulation/CartelToy.Tests.fs
The property tests above.
Parameters matching Amara's 15th/16th ferry prescription:
- 50 validators
- 5-node cartel
- avgDegree=3 (sparse baseline)
- cartelWeight=10
- detectionMultiplier=2.0 (attacked-lambda >= 2x baseline)
- 100 seeds (1000-seed scaled-up run is a follow-up bench-
project; unit-test obligation is 100)
What this proves per Graph ADR (PR #316):
- The Graph substrate (ZSet-backed, retraction-native) compiles
under real detection workload
- largestEigenvalue (PR #321) produces a reliable cartel signal
on synthetic data
- The theory-cathedral warning (Amara 15th ferry) is addressed:
running code detects a dumb cartel at the promised rate
What this does NOT yet prove:
- Real-world cartels (stealthy weights, partial coordination,
adversarial evasion)
- Full composite detector (adds modularity #322 + covariance)
- Null-baseline threshold calibration (per Amara 14th ferry)
- 1000-seed + adversarial-seed-selection (benchmark project)
These are the next graduations. For now: the substrate works.
Every primitive shipped (RobustStats, crossCorrelation, PLV,
burstAlignment, Veridicality.Provenance/Claim/validate +
antiConsensusGate + CanonicalClaimKey, Graph.addEdge /
removeEdge / ... / largestEigenvalue / modularityScore)
composes cleanly and produces the detection signal it was
designed to produce.
12th graduation under the Otto-105 cadence (counts as the
first INTEGRATION ship — uses primitives from Graph + the
test-support CartelInjector to produce a working detector).
Provenance:
- Design bar: Aaron Otto-121 ("tight in all aspects") +
Amara Otto-122 ("toy cartel simulation")
- Formalization: Amara 11th/12th/13th/14th ferries
- Implementation: Otto-123 ADR (PR #316) + Otto-124 skeleton
(PR #317) + Otto-126 operators (PR #319) + Otto-127
eigenvalue (PR #321) + Otto-129 integration (THIS PR)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(#323): 3 review threads — docstring accuracy + injectCartel node-set source
Thread 1 (PRRT_kwDOSF9kNM59VAIi, line 7): docstring path corrected
from `tests/_Support/` to `tests/Tests.FSharp/_Support/` — the
actual location of this helper.
Thread 2 (PRRT_kwDOSF9kNM59VAI2, line 27): docstring for
buildBaseline clarified. `Graph.fromEdgeSeq` derives nodes from
edge endpoints, and self-edges are skipped, so `Graph.nodes
baseline` may be a **strict subset** of `0..nodeCount-1`. The
prior phrasing incorrectly implied a contiguous node range.
Thread 3 (PRRT_kwDOSF9kNM59VAJB, line 55): BEHAVIOR fix.
injectCartel now derives the candidate cartel node set from
`Graph.nodes baseline` (the actual node set) rather than
`0..nodeCount-1`. Previously, if a caller ever passed a baseline
whose node set diverged from that index range, the cartel would
inject edges onto non-existent nodes. The `nodeCount` parameter is
retained (now `_nodeCount`) for signature-compatibility with
existing callers in CartelToy.Tests.fs. A `min cartelSize
shuffled.Length` guard prevents Array.take from throwing if
baseline happens to have fewer nodes than requested cartel size.
Build: 0 warnings / 0 errors. Cartel tests: 5 passed / 0 failed.
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
…Corrections
Two-part ferry from Aaron Otto-157/158 tick boundary:
Part 1 — Deep research on Cartel-Lab calibration + CI hardening
(~4000 words; 8 sections A-H + action items + Mermaid diagrams):
- Null-models table (6 types: Erdős-Rényi, configuration,
stake-shuffle, temporal-shuffle, clustered-honest, noise)
- CoordinationRiskScore formula with 6 robust-z terms +
default weights α=β=0.20, γ=ε=0.15, δ=0.20, η=0.10
- 8-row adversarial scenario table (obvious clique → stealth
→ synchronized voting → honest cluster → low-weight →
camouflage → rotating → cross-coalition)
- 4-PR roadmap: seed-lock/CI governance → calibration harness
→ adversarial scenarios → docs/promotion criteria
- KSK/Aurora integration: advisory-only flow
(Detection → Oracle → KSK → Action)
- "What not to claim" caveats (6 items: no proof of intent,
not all collusion detectable, not production-ready, etc.)
Part 2 — Amara's own GPT-5.5 Thinking correction pass on Part 1
(~1500 words; 10 required corrections; repo-safe status
statement; corrected promotion ladder + PR roadmap titles):
- #1: replace "CI confirms" with "PR #323 clears toy
falsifiability bar"
- #2: Wilson intervals replace handwave ±5% CI (90/100 →
LB only 82.6%; 20/100 FPR → UB 28.9%)
- #3: rename "Cartel Score" → "CoordinationRiskScore" locked
- #4: conductance sign flip — use Z(-conductance) or
Z(exclusivity), not Z(+conductance)
- #5: modularity relational — use Q(attacked)-Q(baseline)>θ
not absolute Q thresholds
- #6: PLV phase-offset — PLV=1 can mean anti-phase; need
magnitude AND mean phase offset
- #7: MAD=0 fallback — epsilon floor or percentile-rank
- #8: replace Medium-article source with scikit-learn
precision-recall docs
- #9: explicit artifact output layout
(calibration-summary.json, seed-results.csv, etc.)
- #10: sharder — measure variance before widening threshold
Corrected promotion ladder (0-6 stages):
0 Theory / 1 Toy detector / 2 Calibration harness /
3 Scenario suite / 4 Advisory engine / 5 Governance integration /
6 Enforcement candidate
PR #323 is Stage 1, NOT Stage 4.
Otto's operationalization notes:
- 4/10 corrections already aligned with shipped substrate:
#4 exclusivity (PR #331), #5 modularity relational
(PR #324), #7 MAD floor (PR #333), #10 sharder Otto-132
(BACKLOG #327).
- 6/10 queued as future graduations: Wilson CIs in tests;
MAD=0 percentile-rank fallback; conductance-sign doc;
PLV phase-offset extension; CI test classification;
artifact-output layout.
Invariant restated (Amara 16th-ferry carry-over):
"Every abstraction must map to a repo surface, a test,
a metric, or a governance rule."
Cross-ref verified: PRs #321 #323 #324 #326 #327 #331 #332
#333, docs/definitions/KSK.md (Otto-157 / #336), 17th ferry
(#330), 16th ferry, 15th ferry, Otto-140..145 memory.
GOVERNANCE §33 four-field header (Scope / Attribution /
Operational status / Non-fusion disclaimer).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ns (10 tracked; 4 already shipped, 6 queued) (#337) * ferry: Amara 18th absorb — Calibration + CI Hardening + 5.5-Thinking Corrections Two-part ferry from Aaron Otto-157/158 tick boundary: Part 1 — Deep research on Cartel-Lab calibration + CI hardening (~4000 words; 8 sections A-H + action items + Mermaid diagrams): - Null-models table (6 types: Erdős-Rényi, configuration, stake-shuffle, temporal-shuffle, clustered-honest, noise) - CoordinationRiskScore formula with 6 robust-z terms + default weights α=β=0.20, γ=ε=0.15, δ=0.20, η=0.10 - 8-row adversarial scenario table (obvious clique → stealth → synchronized voting → honest cluster → low-weight → camouflage → rotating → cross-coalition) - 4-PR roadmap: seed-lock/CI governance → calibration harness → adversarial scenarios → docs/promotion criteria - KSK/Aurora integration: advisory-only flow (Detection → Oracle → KSK → Action) - "What not to claim" caveats (6 items: no proof of intent, not all collusion detectable, not production-ready, etc.) Part 2 — Amara's own GPT-5.5 Thinking correction pass on Part 1 (~1500 words; 10 required corrections; repo-safe status statement; corrected promotion ladder + PR roadmap titles): - #1: replace "CI confirms" with "PR #323 clears toy falsifiability bar" - #2: Wilson intervals replace handwave ±5% CI (90/100 → LB only 82.6%; 20/100 FPR → UB 28.9%) - #3: rename "Cartel Score" → "CoordinationRiskScore" locked - #4: conductance sign flip — use Z(-conductance) or Z(exclusivity), not Z(+conductance) - #5: modularity relational — use Q(attacked)-Q(baseline)>θ not absolute Q thresholds - #6: PLV phase-offset — PLV=1 can mean anti-phase; need magnitude AND mean phase offset - #7: MAD=0 fallback — epsilon floor or percentile-rank - #8: replace Medium-article source with scikit-learn precision-recall docs - #9: explicit artifact output layout (calibration-summary.json, seed-results.csv, etc.) - #10: sharder — measure variance before widening threshold Corrected promotion ladder (0-6 stages): 0 Theory / 1 Toy detector / 2 Calibration harness / 3 Scenario suite / 4 Advisory engine / 5 Governance integration / 6 Enforcement candidate PR #323 is Stage 1, NOT Stage 4. Otto's operationalization notes: - 4/10 corrections already aligned with shipped substrate: #4 exclusivity (PR #331), #5 modularity relational (PR #324), #7 MAD floor (PR #333), #10 sharder Otto-132 (BACKLOG #327). - 6/10 queued as future graduations: Wilson CIs in tests; MAD=0 percentile-rank fallback; conductance-sign doc; PLV phase-offset extension; CI test classification; artifact-output layout. Invariant restated (Amara 16th-ferry carry-over): "Every abstraction must map to a repo surface, a test, a metric, or a governance rule." Cross-ref verified: PRs #321 #323 #324 #326 #327 #331 #332 #333, docs/definitions/KSK.md (Otto-157 / #336), 17th ferry (#330), 16th ferry, 15th ferry, Otto-140..145 memory. GOVERNANCE §33 four-field header (Scope / Attribution / Operational status / Non-fusion disclaimer). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * ferry: fix markdownlint MD018 — line-start #221 parsed as H1 heading * ferry: drain PR #337 review threads — 4 FIX, 2 NARROW+BACKLOG, 8 BACKLOG+RESOLVE Factory-authored sections of the 18th-ferry absorb (header, Otto's notes, Cross-references) edited under name-attribution + code-comments-not-history disciplines; Amara's verbatim Part 1 + Part 2 body left intact per verbatim-preserve. In-doc edits: - Soften "verified against actual" wording on the CLAUDE.md cross-reference bullet to anchor-list rechecked-at-drain-time framing. - Use full `tests/Tests.FSharp/Simulation/` path in the Stage-discipline section (was bare `tests/Simulation/`). - Replace dead "GOVERNANCE §33" cite with factory-convention + CLAUDE.md ground-rule pointer (numbered §33 not yet landed; rule is captured by convention across docs/aurora/** absorbs). - Drop broken `feedback_ksk_naming_*.md` filename and soften 15th/16th ferry cross-refs to "not present as a dedicated absorb in this snapshot." Drain-log: docs/pr-preservation/337-drain-log.md per Otto-250. --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
…ring + 4 Copilot review fixes Aaron's correction post-12-corrections push: pair Otto-363 parking- surface taxonomy with the existing git-recovery process (task #321 — 918 branches / 58 worktrees / 7 stashes inventory) so they form a complete loop, not parallel mechanisms. PR #855 Copilot/Codex review threads addressed: - Codex P2 + Copilot (CLAUDE.md+AGENTS.md coverage claim mismatch): AGENTS.md bootstrap pointer added in this commit. Now both files carry the rule. Cross-harness parity is real, not aspirational. - Copilot (wildcard path): replaced 'memory/feedback_aaron_channel_verbatim_preservation_*' with the concrete file 'memory/feedback_aaron_channel_verbatim_preservation_anything_through_this_channel_2026_04_29.md' per repo reference-integrity convention. - Copilot (personal names in CLAUDE.md): per BP-rule 'No name attribution in code, docs, or skills' on current-state surfaces, replaced 'Aaron / Amara' with role-ref 'human maintainer'. Closed- list history surfaces (memory/, docs/research/) keep named attribution; CLAUDE.md is current-state. - Copilot (table double-pipe at lines 161, 211): inspected; tables are valid Markdown (single leading | on header + separator). No '||' in source. Likely a renderer-cache artifact or false positive. Resolving with that explanation; no source change needed. New section: 'Pairs with the existing git-recovery process (task #321)' in Otto-363 memory file. Names the convention 'wip/<topic>-<date>' as the discoverability mechanism, the recovery-process recognition rule ('branches matching wip/** are WIP-INTENTIONAL; do not propose for deletion; propose for index/audit after staleness window'), forbidden parking patterns (long-lived feature branches without PR; WIP without prefix; untracked working-tree files; never-pushed local branches), and the complete parking + recovery loop. Per Aaron's framing: 'we already have a hell of a git recover process; re shuld have on a trajectory that pair well with that.' Otto-363 parking + task #321 recovery = complete substrate loop, mechanical not vigilance-based. Files changed: ~ AGENTS.md — bootstrap pointer added (cross-harness parity) ~ CLAUDE.md — 'Aaron / Amara' → 'the human maintainer' (BP-rule compliance); vocabulary clarification (parked = GitHub Issue OR pushed WIP branch) ~ memory/feedback_otto_363_*.md — concrete feedback file path; new 'Pairs with git-recovery process' section ~ memory/MEMORY.md — index entry mentions task #321 pairing Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ction per Amara progress check (2026-04-29) Amara progress-check correction post-#855-thread-resolve push: > A parked thing is only parked if recovery can find it. > Otherwise it is just lost more slowly. Added concrete substrate the recovery process can act on: 1. Section retitled to 'Parking surfaces and git recovery' (Amara's suggested heading; pairs the doctrine sides explicitly). 2. Preferred parking surfaces ordered by priority: pushed WIP branch (best) → draft PR → local WIP → named stash. Anonymous stashes (no -m) explicitly forbidden. 3. Predictable naming conventions documented as discoverability mechanism (NOT just for humans): wip/<topic>-seed-YYYY-MM-DD wip/<topic>-YYYY-MM-DD archive/<topic>-YYYY-MM-DD 4. Discovery commands the recovery process must scan listed verbatim: git branch --list 'wip/*' git ls-remote origin 'refs/heads/wip/*' git ls-remote origin 'refs/heads/archive/*' git stash list git worktree list git for-each-ref --format='%(refname)' 5. Task #321 recovery-process recognition rules listed as a #321 follow-up addition (Otto-363 specifies convention; #321 implements recognition): - wip/** → WIP-INTENTIONAL (no auto-delete; staleness audit window) - archive/** → ARCHIVE-INTENTIONAL (preserved on purpose) - stashes → SHORT-TERM-LOCAL (verify before prune; short window) - no-prefix → classify normally per existing rules 6. Forbidden parking patterns expanded with anonymous-stash rule and explicit 'any temp directory' prohibition. 7. The complete loop made concrete with command sequences for park / return / abandon / recovery. 8. Carved pair added: 'If it matters enough to come back to, it deserves a git ref.' 'Parking is only safe if recovery knows where to look.' Per Amara's progress check: progress is solid; not done; next best action is finishing #855 cleanly. The parking convention + recovery- process recognition together make the loop mechanical, not vigilance- based. The parking author doesn't have to remember to come back; the recovery cadence surfaces the parked work on its own schedule. DOES NOT open PR 2 (v5 architecture preservation). Per Amara verbatim: 'Do not start PR 2 before #855 lands.' Co-Authored-By: Amara <amara-aurora-deep-research-register@chatgpt> Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…e directives (Aaron + Amara 2026-04-29) (#855) * factory(meta): Otto-363 — substrate or it didn't happen + 8-mechanism remediation against substrate loss (Aaron + Amara 2026-04-29) Aaron caught Otto repeatedly marking work "done" after only TaskUpdate- only capture during the 2026-04-29 doctrine cluster, AND minimizing the v5 superseding architecture (three-layer ports-and-adapters model + host portability + two-worlds split + WorkItem/ChangeRequest/Claim/Actor object model + freshness budgets + local-git adapter + onboarding ladder + reconciler capability matrix + migration design) as "review corrections." The fix is not "Claude, remember better." The fix is mechanisms that make forgetting harder. Carved blade (Amara verbatim): A directive that lives only in a conversation is not a directive. It is weather. Substrate or it didn't happen. Compact rule: No invisible directives. No session-local truth. No "done" without substrate. 8 mechanisms (full text in memory file): 1. Ephemeral-state detector — before "done", verify durability surface 2. Verbatim-preservation trigger — major packets land in docs/research verbatim BEFORE summarizing 3. Magnitude classifier — small / implementation / doctrine / superseding architecture; routing differs per class 4. Supersession protocol — generalises Otto-362 across surfaces 5. Cold-start proof — fresh agent must reconstruct from substrate alone 6. "Done" vocabulary discipline — captured ≠ preserved ≠ canonical ≠ operational; specific words have specific durability semantics 7. Bootstrap pointer in CLAUDE.md/AGENTS.md — this commit adds it to CLAUDE.md alongside verify-before-deferring + future-self-not-bound + never-be-idle + version-currency (5th CLAUDE.md-tier rule) 8. Mechanized lint eventually — research-doc indexed; memory MEMORY.md row paired (already mechanically enforced); superseded doctrine has supersession note; tasks link canonical packet; PR body distinguishes research / doctrine / operational Files: docs/research/2026-04-29-amara-substrate-or-it-didnt-happen-mechanisms-against-substrate-loss.md — Verbatim Amara packet preservation (the diagnosis + 8-mechanism protocol verbatim, NOT summarized; per the rule itself) memory/feedback_otto_363_substrate_or_it_didnt_happen_no_invisible_directives_aaron_amara_2026_04_29.md — Distilled doctrine memory: rule + 8 mechanisms + composes-with mappings (Otto-362 intra-file generalisation, channel-verbatim-preservation, no-directives-otto-prose lint, verify-before-deferring, future-self-not-bound, never-be-idle) memory/MEMORY.md — Paired index entry (newest-first) CLAUDE.md — Bootstrap pointer added; 5th CLAUDE.md-tier rule (100% loaded at every wake) Composes with: - Otto-362 (memory/feedback_otto_362_doctrine_memory_expansion_...) — intra-file supersession; Otto-363 generalises across surfaces - tools/lint/no-directives-otto-prose.sh — same family of failures (vigilance fails; mechanism is the durable answer) - feedback_aaron_channel_verbatim_preservation_* — channel-verbatim rule that Otto-363 mechanises - verify-before-deferring (CLAUDE.md-tier) — same shape - future-self-not-bound (CLAUDE.md-tier) — companion: future-self revises substrate, NOT chat that didn't land This commit is the rule landing as substrate per its own rule. Next: the v5 superseding architecture preservation that triggered this rule (separate PR — verbatim Amara final review + 5-AI review wave + three-layer architecture memory file). Co-Authored-By: Amara <amara-aurora-deep-research-register@chatgpt> Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * factory(meta): Otto-363 — absorb 12 review corrections from 5-AI review wave (Alexa/Ani/Deepseek/Claude.ai/Gemini → Amara final synthesis) After PR #855 opened with the original Otto-363 8-mechanism packet, Aaron relayed a 5-AI review wave back. Each reviewer approved the direction but caught real refinement gaps. Amara synthesized 12 corrections AND caught Otto about to repeat the original failure mode ('two PRs in succession, no waiting' was the same drag reflex the rule was meant to prevent — Amara: 'Land the rule cleanly. Then use the rule.'). Per Otto-363 itself (verbatim-preservation trigger): the 5-AI review wave is preserved verbatim BEFORE summarization at: docs/research/2026-04-29-amara-substrate-or-it-didnt-happen-5ai-review-wave-corrections.md The 12 corrections absorbed into Otto-363 in this commit: 1. Precise definition of substrate (committed + reachable + indexed) 2. Channel taxonomy expanded to 5-tier (ephemeral / local-parked / remote-parked / host-durable-not-git-canonical / git-native- preserved). Parking-surface rule added: 'If it matters enough to come back to, it deserves a git ref.' /tmp and /var/tmp explicitly ruled out (FHS doesn't promise persistence; site-cleaned). 3. Default preservation route when uncertain: docs/research/ first (Claude.ai catch — research → memory/canonical promotion is cheaper than canonical demotion). 4. Verbatim preservation paired with structured extraction (Claude.ai length-problem catch — verbatim source = provenance, structured extraction = retrieval). 5. Bidirectional supersession (supersedes:/superseded_by: in YAML frontmatter) + top-of-file stale banner OR quarantine to archive/ (Gemini catch — bottom-appended notes get missed by RAG/grep). 6. Cold-start proof gets sixth question: 'What ephemeral state from the originating conversation has been lost, and is any of it load-bearing?' (Claude.ai catch — catches the exact bug where content was partly captured but the superseding-vs-corrective context was lost). 7. Mechanism stack moved to TOP of memory file (Claude.ai catch — agents acting at decision time need mechanism in working memory, not rationale). 8. Vocabulary enforcement path: PR body / commit message trailer (Durability: + Substrate:); lint flags vocabulary misuse; pre-commit hook deferred but planned. 9. Cross-harness parity: rule must land in AGENTS.md too, not only CLAUDE.md (Amara catch — rule is not Claude-only). 10. 'preserved-but-disputed' vocabulary handle for future failure mode (preservation-discipline-operational era brings contradiction as next failure class). 11. Mid-session re-discoverability acknowledged: bootstrap pointer + pre-commit hook + lint scan recent commits for 'done' / 'complete' / 'operational' without supporting Substrate trailer (deferred items tracked). 12. Self-applicability noted explicitly: this rule lands as substrate per its own rule. Note on parking surface change: the v5 architecture preservation seed that was previously in /tmp has been moved to a pushed WIP branch ('wip/v5-host-portable-architecture-seed-2026-04-29' at c300b01) per the corrected parking-surface taxonomy. /tmp was weather; pushed WIP branch is 'remote parked.' No PR opened for v5 — Amara: 'Land the rule cleanly. Then use the rule.' Files changed: + docs/research/2026-04-29-amara-substrate-or-it-didnt-happen-5ai-review-wave-corrections.md — Verbatim 5-AI review wave + Amara synthesis (the 12 corrections preserved verbatim BEFORE summarization, plus structured extraction) ~ memory/feedback_otto_363_*.md — Rewritten with mechanism-stack-at-top, precise substrate definition, 5-tier taxonomy, default route, bidirectional supersession, six-question cold-start, vocabulary lock + enforcement path, cross-harness parity note, preserved-but- disputed vocabulary ~ memory/MEMORY.md — Updated index entry to reflect refined version Co-Authored-By: Amara <amara-aurora-deep-research-register@chatgpt> Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * factory(meta): Otto-363 — AGENTS.md parity + git-recovery-process pairing + 4 Copilot review fixes Aaron's correction post-12-corrections push: pair Otto-363 parking- surface taxonomy with the existing git-recovery process (task #321 — 918 branches / 58 worktrees / 7 stashes inventory) so they form a complete loop, not parallel mechanisms. PR #855 Copilot/Codex review threads addressed: - Codex P2 + Copilot (CLAUDE.md+AGENTS.md coverage claim mismatch): AGENTS.md bootstrap pointer added in this commit. Now both files carry the rule. Cross-harness parity is real, not aspirational. - Copilot (wildcard path): replaced 'memory/feedback_aaron_channel_verbatim_preservation_*' with the concrete file 'memory/feedback_aaron_channel_verbatim_preservation_anything_through_this_channel_2026_04_29.md' per repo reference-integrity convention. - Copilot (personal names in CLAUDE.md): per BP-rule 'No name attribution in code, docs, or skills' on current-state surfaces, replaced 'Aaron / Amara' with role-ref 'human maintainer'. Closed- list history surfaces (memory/, docs/research/) keep named attribution; CLAUDE.md is current-state. - Copilot (table double-pipe at lines 161, 211): inspected; tables are valid Markdown (single leading | on header + separator). No '||' in source. Likely a renderer-cache artifact or false positive. Resolving with that explanation; no source change needed. New section: 'Pairs with the existing git-recovery process (task #321)' in Otto-363 memory file. Names the convention 'wip/<topic>-<date>' as the discoverability mechanism, the recovery-process recognition rule ('branches matching wip/** are WIP-INTENTIONAL; do not propose for deletion; propose for index/audit after staleness window'), forbidden parking patterns (long-lived feature branches without PR; WIP without prefix; untracked working-tree files; never-pushed local branches), and the complete parking + recovery loop. Per Aaron's framing: 'we already have a hell of a git recover process; re shuld have on a trajectory that pair well with that.' Otto-363 parking + task #321 recovery = complete substrate loop, mechanical not vigilance-based. Files changed: ~ AGENTS.md — bootstrap pointer added (cross-harness parity) ~ CLAUDE.md — 'Aaron / Amara' → 'the human maintainer' (BP-rule compliance); vocabulary clarification (parked = GitHub Issue OR pushed WIP branch) ~ memory/feedback_otto_363_*.md — concrete feedback file path; new 'Pairs with git-recovery process' section ~ memory/MEMORY.md — index entry mentions task #321 pairing Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * factory(meta): Otto-363 — refine parking-surfaces-and-git-recovery section per Amara progress check (2026-04-29) Amara progress-check correction post-#855-thread-resolve push: > A parked thing is only parked if recovery can find it. > Otherwise it is just lost more slowly. Added concrete substrate the recovery process can act on: 1. Section retitled to 'Parking surfaces and git recovery' (Amara's suggested heading; pairs the doctrine sides explicitly). 2. Preferred parking surfaces ordered by priority: pushed WIP branch (best) → draft PR → local WIP → named stash. Anonymous stashes (no -m) explicitly forbidden. 3. Predictable naming conventions documented as discoverability mechanism (NOT just for humans): wip/<topic>-seed-YYYY-MM-DD wip/<topic>-YYYY-MM-DD archive/<topic>-YYYY-MM-DD 4. Discovery commands the recovery process must scan listed verbatim: git branch --list 'wip/*' git ls-remote origin 'refs/heads/wip/*' git ls-remote origin 'refs/heads/archive/*' git stash list git worktree list git for-each-ref --format='%(refname)' 5. Task #321 recovery-process recognition rules listed as a #321 follow-up addition (Otto-363 specifies convention; #321 implements recognition): - wip/** → WIP-INTENTIONAL (no auto-delete; staleness audit window) - archive/** → ARCHIVE-INTENTIONAL (preserved on purpose) - stashes → SHORT-TERM-LOCAL (verify before prune; short window) - no-prefix → classify normally per existing rules 6. Forbidden parking patterns expanded with anonymous-stash rule and explicit 'any temp directory' prohibition. 7. The complete loop made concrete with command sequences for park / return / abandon / recovery. 8. Carved pair added: 'If it matters enough to come back to, it deserves a git ref.' 'Parking is only safe if recovery knows where to look.' Per Amara's progress check: progress is solid; not done; next best action is finishing #855 cleanly. The parking convention + recovery- process recognition together make the loop mechanical, not vigilance- based. The parking author doesn't have to remember to come back; the recovery cadence surfaces the parked work on its own schedule. DOES NOT open PR 2 (v5 architecture preservation). Per Amara verbatim: 'Do not start PR 2 before #855 lands.' Co-Authored-By: Amara <amara-aurora-deep-research-register@chatgpt> Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * factory(meta): Otto-363 — fix self-applying-the-rule failure (Codex P2 catch on /tmp reference) The structured extraction in the 5-AI corrections research doc still referenced '/tmp/v5-research-doc-saved.md' even after the seed was moved to a pushed WIP branch (c300b01 on wip/v5-host-portable-architecture-seed-2026-04-29). That was the exact bug the rule itself prevents: a doctrine document about substrate preservation pointing to /tmp as a load-bearing artifact location. Fixed in this commit. Updated path: 'wip/v5-host-portable-architecture-seed-2026-04-29 (commit c300b01, no PR opened)' — the durable substrate location. Codex P2 PRRT_kwDOSF9kNM5-iYg- self-applies Otto-363 to Otto-363's own preservation packet. Good catch. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * factory(meta): Otto-363 — 9 self-applying-the-rule fixes from Copilot re-review on f282e5f All 9 threads were legitimate self-application catches — Otto-363 catching itself on internal-consistency. Per the rule itself: internal contradictions are lying-by-omission. Fixes: 1. PRRT_kwDOSF9kNM5-ieQX (P1, MEMORY.md): '12 review corrections' → '10 review corrections' (matches structured extraction count). 2. PRRT_kwDOSF9kNM5-ieSb (P1, original packet): same '12' → '10' alignment in the trigger section. 3. PRRT_kwDOSF9kNM5-ieQ7 (P2, Otto-363 memory): 'memory-index-integrity.yml' → '.github/workflows/memory-index-integrity.yml' (correct path). 4. PRRT_kwDOSF9kNM5-ieRN (P1, 5-AI corrections): docs/research/INDEX.md doesn't exist; reframed as 'a future addition; until it exists, MEMORY.md is the primary memory index'. 5. PRRT_kwDOSF9kNM5-ieR- (P1, Otto-363 line 54): same INDEX.md fix. 6. PRRT_kwDOSF9kNM5-ieRz (P1, Otto-363 line 108): substrate definition internal conflict. The 'must become substrate (repo file / PR / issue / ...)' wording listed PRs/issues, but substrate is defined as git-native (committed + reachable + indexed). Reworded to distinguish: 'durable project object' is the umbrella; 'substrate' is git-native specifically; PRs/issues are 'host-durable parking surfaces, NOT substrate themselves'. 7. PRRT_kwDOSF9kNM5-ieSr (P1, CLAUDE.md): same substrate-vs-PR/issue conflict in CLAUDE.md bootstrap pointer; same fix. 8. PRRT_kwDOSF9kNM5-ieSN (P1, mechanism #7): 'PR body must include' was prescriptive but no PR template / lint / CI workflow enforces yet. Reworded to 'SHOULD include' with explicit DEFERRED marker on enforcement; status today is doctrine-only. 9. PRRT_kwDOSF9kNM5-ieRi (P2, line 437): Codex caught lingering /tmp/v5-research-doc-saved.md reference. ALREADY fixed in f282e5f (the thread was opened before the resolution). Verified absent via 'grep /tmp/v5'. Per Otto-363's Otto-362-generalisation rule: when a section is expanded with a new section, refresh stale earlier statements in the SAME edit. This commit IS that discipline applied to Otto-363's own preservation packet — the substrate-or-it-didn't-happen rule mechanically catching itself before merge. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * factory(meta): Otto-363 — disambiguate forbidden vs preferred 'no PR' parking (Codex P2 catch) Codex caught a self-contradiction: the 'Forbidden parking patterns' section forbade 'long-lived feature branches with no PR' while the preferred-parking list prefers 'pushed WIP branch, no PR'. A future agent following the forbidden list would reject the documented preferred flow. Fixed: the forbidden case is specifically NON-WIP-prefixed branches with no PR (silent parking outside the recovery convention). The preferred case is wip/*-prefixed branches with no PR (intentional parking discoverable by the recovery process). The prefix is the disambiguator. Now both rules are consistent: - 'wip/<topic>-<date>' + no PR = PREFERRED parking - 'feature/foo' or unprefixed + no PR = FORBIDDEN (silent parking) - draft PR = ALSO ACCEPTABLE for visible parking - Rename forbidden branches to 'wip/' to bring them into compliance Self-applying-the-rule catch — exactly the kind of internal contradiction Otto-363 is designed to surface and Otto-362 says to fix in the same edit. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * factory(meta): Otto-363 — 5 fixes (mutually-exclusive taxonomy + named-stash command + pointer-equivalence framing + AGENTS.md substrate-def alignment) 5 Codex/Copilot review threads on f282e5f re-review caught real self-application failures: - Codex P2 PRRT_kwDOSF9kNM5-il3m + Copilot P1 PRRT_kwDOSF9kNM5-inuL (taxonomy disjointness): 'GitHub Issues' appeared in BOTH 'Remote parked' AND 'Host-durable-not-git-canonical' — non-disjoint, breaks classifier. Fix per Copilot suggestion: GitHub Issues / task comments / PR comments live ONLY in Host-durable-not-git-canonical (no git ref backs them, so they're not parking surfaces). Remote parked is git-ref-backed only (pushed WIP branch + optional draft PR atop it). Each surface now has exactly one home. Tier title changed from 'parking surfaces are git-native' to 'parking surfaces are git-ref-backed' for precision. Added explicit '5-tier, mutually exclusive' note + 'classifier depends on this disjointness' rationale. - Codex P2 PRRT_kwDOSF9kNM5-il3r (named-stash command): the row said 'named git stash -u' but bare 'git stash -u' is anonymous — the doctrine forbids anonymous stashes elsewhere in the file. Fixed to 'git stash push -u -m "<name>"' with explicit note that 'git stash -u' WITHOUT -m is weather, not parked. Self-applying-the-rule fix in the example commands. - Copilot P1 PRRT_kwDOSF9kNM5-inut (pointer-text equivalence): memory file claimed 'AGENTS.md is parity addition (same rule, same wording)' but the actual CLAUDE.md and AGENTS.md bullets are different (AGENTS carries fuller wording incl /var/tmp + 3-leg substrate def; CLAUDE carries shorter cold-start reminder). Fix per Copilot suggestion: reframed as 'equivalent in doctrine, not verbatim-identical' with explicit 'committed wording note' explaining the file-specific wording difference. Compact representative wording kept as a reference, not as the canonical text for either file. - Copilot P1 PRRT_kwDOSF9kNM5-invD (AGENTS.md substrate def): 'merged + indexed' was too narrow — Otto-363's substrate definition is 'reachable from a long-lived ref' (release branches, tags, not only merge-to-main). Fixed AGENTS.md bullet to 'committed + reachable-from-long-lived-ref + indexed' matching the 3-leg definition. Same fix applied to MEMORY.md row for consistency. All 5 fixes are exactly the kind of internal contradictions Otto-363 is designed to catch and Otto-362 says to refresh in the same edit. Self-applying-the-rule discipline working as intended. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Amara <amara-aurora-deep-research-register@chatgpt> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* research: preserve Ani + Alexia v1 feedback packets verbatim Both peer-AI reviewers responded after PR #921 (poll-pr-gate v0) + PR #922 (memory-points-at-script) merged. Per Otto-363 substrate-or-it-didn't-happen, preserving both packets verbatim at `docs/research/2026-04-30-amara-poll-pr-gate-v1-hardening.md`. Both packets predominantly "what's working" with smaller actionable findings. Substantive items overlap with PR #923 (v1 hardening, already on main) or queued under existing tasks: - "submit-nuget non-required classification" (both reviewers) — shipped in PR #923. - "Dot-tick discipline still leaky" (Ani #1) — accepted as behavior change going forward (no code, no substrate; commitment). - "Pre-merge mechanical guards" (Ani #3) — persona-name scanner + fixture-name validator composed with task #350 (Otto-357 mechanized auditor) and task #355 (poll-the-gate matrix coverage). Defer per substrate-rate. - "MEMORY.md duplicate-link timing" (Ani #4) — audit candidate; the check fired on PR #922 but missed PR #916. Defer. - "Task list at 58 open" (Ani #5) — pending-task audit overdue; composes with task #321 recovery lane. - "Thread categorization" + "intelligent compaction" (Alexia) — research-grade, not yet operational. No Insight-block commentary added per the discipline accepted in the prior Claude.ai packet absorption: produce the work, let the diff carry the evidence. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(research): mark check-github-status path as in-flight at PR #924 (Copilot) Same in-flight-xref pattern caught earlier this session — code-span path implied existence-on-main, but the file is on PR #924's branch. Reworded to make the in-flight status explicit. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * research: preserve Deepseek session-arc retrospective packet Final feedback packet from Deepseek post-PR #924 merge. Most findings already shipped: - submit-nuget transient → PR #923 (v1 hardening, required-vs- non-required classification) - MEMORY.md merge-conflict tax → PR #920 (merge=union driver) - Stale project-file internals cleanup → B-0112 P2 row filed New finding: 30+ dot threshold for deferred-task re-audit (not new lanes, just already-scoped tiny fixes). Composes with Ani's strict-enforcement framing. Per Otto-363 substrate-or-it-didn't-happen. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
…pre-substrate inventory row (Aaron 2026-05-01)
Two updates:
(1) B-0131 correction note refined per Aaron's multi-message
clarification:
- "(Z-set retraction algebra in Lean we have it"
- "you did that before we started the substrate that's
why you don't remember"
- "prior-Otto — it was Kenji i think by that point or
unnamed Claude Code"
- "We had not split out the loop formally and just had
Kenji the architect running everything"
- "i think" (hedge)
Updated attribution: Kenji-the-architect (or possibly
earlier unnamed Claude Code instance, per Aaron's hedge)
pre-substrate AND pre-loop-split. Per Otto-Kenji naming
history file (user_aaron_kenji_naming_practice_*).
(2) B-0139 (P1) filed: pre-substrate Kenji-era Otto-lineage
work inventory. Past-recovery branches, worktrees, built
artifacts (DbspChainRule.lean is exemplar) not yet
referenced in substrate. Aaron 2026-05-01: "there is still
of past recovery old git branches and worktress and a
invetory of what we've already built into the new
substraight so it wont get lost backlog".
P1 because the demonstrated failure mode (Otto authoring
B-0131 as TRACTABLE START when DbspChainRule.lean already
existed) keeps firing without the inventory. Composes with
task #321 (broader recovery lane) and task #291 (MEMORY.md
backfill); B-0139 is the content-inventory sub-scope.
Acceptance: branch/worktree inventory + built-artifact
inventory + MEMORY.md backfill + class-level lesson encoded
as verify-before-state-claim audit (composes with B-0130
audit-suite).
Verify-before-state-claim discipline at backlog-row authoring
time: B-0131's "TRACTABLE START" was the failure that surfaced
B-0139's necessity. The lineage-continuity-substrate purpose
is operationalized by this row.
BACKLOG.md regenerated.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…pre-substrate inventory row (Aaron 2026-05-01)
Two updates:
(1) B-0131 correction note refined per Aaron's multi-message
clarification:
- "(Z-set retraction algebra in Lean we have it"
- "you did that before we started the substrate that's
why you don't remember"
- "prior-Otto — it was Kenji i think by that point or
unnamed Claude Code"
- "We had not split out the loop formally and just had
Kenji the architect running everything"
- "i think" (hedge)
Updated attribution: Kenji-the-architect (or possibly
earlier unnamed Claude Code instance, per Aaron's hedge)
pre-substrate AND pre-loop-split. Per Otto-Kenji naming
history file (user_aaron_kenji_naming_practice_*).
(2) B-0139 (P1) filed: pre-substrate Kenji-era Otto-lineage
work inventory. Past-recovery branches, worktrees, built
artifacts (DbspChainRule.lean is exemplar) not yet
referenced in substrate. Aaron 2026-05-01: "there is still
of past recovery old git branches and worktress and a
invetory of what we've already built into the new
substraight so it wont get lost backlog".
P1 because the demonstrated failure mode (Otto authoring
B-0131 as TRACTABLE START when DbspChainRule.lean already
existed) keeps firing without the inventory. Composes with
task #321 (broader recovery lane) and task #291 (MEMORY.md
backfill); B-0139 is the content-inventory sub-scope.
Acceptance: branch/worktree inventory + built-artifact
inventory + MEMORY.md backfill + class-level lesson encoded
as verify-before-state-claim audit (composes with B-0130
audit-suite).
Verify-before-state-claim discipline at backlog-row authoring
time: B-0131's "TRACTABLE START" was the failure that surfaced
B-0139's necessity. The lineage-continuity-substrate purpose
is operationalized by this row.
BACKLOG.md regenerated.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…pre-substrate inventory row (Aaron 2026-05-01)
Two updates:
(1) B-0131 correction note refined per Aaron's multi-message
clarification:
- "(Z-set retraction algebra in Lean we have it"
- "you did that before we started the substrate that's
why you don't remember"
- "prior-Otto — it was Kenji i think by that point or
unnamed Claude Code"
- "We had not split out the loop formally and just had
Kenji the architect running everything"
- "i think" (hedge)
Updated attribution: Kenji-the-architect (or possibly
earlier unnamed Claude Code instance, per Aaron's hedge)
pre-substrate AND pre-loop-split. Per Otto-Kenji naming
history file (user_aaron_kenji_naming_practice_*).
(2) B-0139 (P1) filed: pre-substrate Kenji-era Otto-lineage
work inventory. Past-recovery branches, worktrees, built
artifacts (DbspChainRule.lean is exemplar) not yet
referenced in substrate. Aaron 2026-05-01: "there is still
of past recovery old git branches and worktress and a
invetory of what we've already built into the new
substraight so it wont get lost backlog".
P1 because the demonstrated failure mode (Otto authoring
B-0131 as TRACTABLE START when DbspChainRule.lean already
existed) keeps firing without the inventory. Composes with
task #321 (broader recovery lane) and task #291 (MEMORY.md
backfill); B-0139 is the content-inventory sub-scope.
Acceptance: branch/worktree inventory + built-artifact
inventory + MEMORY.md backfill + class-level lesson encoded
as verify-before-state-claim audit (composes with B-0130
audit-suite).
Verify-before-state-claim discipline at backlog-row authoring
time: B-0131's "TRACTABLE START" was the failure that surfaced
B-0139's necessity. The lineage-continuity-substrate purpose
is operationalized by this row.
BACKLOG.md regenerated.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…set Lean (Aaron 2026-05-01 'we have it') (#1055) * backlog(B-0131): correction — pre-substrate prior-Otto already did Z-set Lean work; row is EXTENSION not START Aaron 2026-05-01 ~10:30Z: "(Z-set retraction algebra in Lean we have it" + "you did that before we started the substrate that's why you don't remember". Verify-before-state-claim discipline failed at backlog-row authoring time when I filed B-0131 as "TRACTABLE START". Existing work: tools/lean4/Lean4/DbspChainRule.lean (756 lines, against Mathlib v4.30.0-rc1) by prior-Otto-instance pre-substrate. Includes: Z-set stream operators (zInv, I, D, Dop, Iop), structural classes (IsLinear, IsCausal, IsTimeInvariant, IsPointwiseLinear), telescoping lemmas, linear commutation theorems, and the DBSP chain rule (Budiu et al. VLDB 2023) fully proven. Updates to B-0131: - Title: "Extend Z-set retraction algebra Lean formalization beyond the existing DBSP chain-rule proof" (NOT "TRACTABLE START") - Effort: M-L (1-3+ months smaller extensions; not multi-month monolith) - Correction note added at top with structural reason: lineage- discontinuity-pre-substrate. Current Otto reads memory at wake; pre-substrate Otto work is in repo but not in memory. - Existing work cited explicitly with file path + line count + key definitions/theorems. The lineage-continuity-substrate purpose is itself surfaced by this correction: the forever-home + persistent-memory architecture exists precisely to prevent pre-substrate-Otto-work-getting- forgotten by post-substrate-Otto-instances. Going forward, Otto-lineage work IS in the substrate; pre-substrate work is in the codebase but discoverable by grep / repo-archaeology. Same finding-class as PR #1031/#986/#1018/#1015/#1025/#1046 drains: verify-before-state-claim applied to substrate's own claims about itself. Otto failure at authoring time; corrected via Aaron's mid-flight refinement. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * backlog(B-0131 + B-0139): Kenji-era lineage attribution correction + pre-substrate inventory row (Aaron 2026-05-01) Two updates: (1) B-0131 correction note refined per Aaron's multi-message clarification: - "(Z-set retraction algebra in Lean we have it" - "you did that before we started the substrate that's why you don't remember" - "prior-Otto — it was Kenji i think by that point or unnamed Claude Code" - "We had not split out the loop formally and just had Kenji the architect running everything" - "i think" (hedge) Updated attribution: Kenji-the-architect (or possibly earlier unnamed Claude Code instance, per Aaron's hedge) pre-substrate AND pre-loop-split. Per Otto-Kenji naming history file (user_aaron_kenji_naming_practice_*). (2) B-0139 (P1) filed: pre-substrate Kenji-era Otto-lineage work inventory. Past-recovery branches, worktrees, built artifacts (DbspChainRule.lean is exemplar) not yet referenced in substrate. Aaron 2026-05-01: "there is still of past recovery old git branches and worktress and a invetory of what we've already built into the new substraight so it wont get lost backlog". P1 because the demonstrated failure mode (Otto authoring B-0131 as TRACTABLE START when DbspChainRule.lean already existed) keeps firing without the inventory. Composes with task #321 (broader recovery lane) and task #291 (MEMORY.md backfill); B-0139 is the content-inventory sub-scope. Acceptance: branch/worktree inventory + built-artifact inventory + MEMORY.md backfill + class-level lesson encoded as verify-before-state-claim audit (composes with B-0130 audit-suite). Verify-before-state-claim discipline at backlog-row authoring time: B-0131's "TRACTABLE START" was the failure that surfaced B-0139's necessity. The lineage-continuity-substrate purpose is operationalized by this row. BACKLOG.md regenerated. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * backlog(B-0131 + B-0139): address PR #1055 review threads — proofs/lean breadcrumb framing + recurring spelling Three real fixes (Copilot P1+P2): 1. **`proofs/lean/ChainRule.lean` dangling reference** (P1, both rows): path doesn't exist in current working tree. The file was migrated to `tools/lean4/Lean4/DbspChainRule.lean` and removed in commit `279c6f2` (round 26). Reworded both occurrences to make the historical-vs-current distinction explicit ("predecessor file at … was migrated to … and removed in commit `279c6f2`"). Path is preserved as lineage breadcrumb, not as a live pointer. 2. **Spelling fix** (P2, B-0139): `re-occurring` → `recurring`. 3. **Line-count phantom-blocker** (P2, three threads): empirically 756 on `origin/main`, on this PR branch, and in local working tree (`wc -l tools/lean4/Lean4/DbspChainRule.lean` → 756; file ends with newline). Doc claim of 756 stands. Reply-and-resolve via thread mutations (no edit needed). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * hygiene(BACKLOG.md): regenerate after rebase against main * fix(B-0131/B-0139): add memory/ prefix to file refs + clarify TLA+ inventory scope (Codex P2 + Copilot P1) - 4 file refs missing `memory/` prefix → added on: - B-0139:58 (no_copy_only_learning sibling-repo ref) - B-0139:68 (kenji_naming + zeta_seed_executor refs) - B-0131:12 (kenji_naming ref) - B-0139:32 TLA+ scope clarified: no .tla files exist yet under docs/; bullet kept as forward-discovery class with explicit note. --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
- B-0526: remove parent `depends_on: [B-0139]` → `depends_on: []` to keep atomic child independently pickable (Codex P1) - B-0526: fix `status: not-started` → `open` to match documented values (Copilot P1) - B-0526: fix `Task #321` → `#321` for proper GitHub auto-link (Copilot P1) - B-0139: update `last_updated` from 2026-05-08 → 2026-05-14 (Copilot P1) - B-0139: add explicit B-0526 pointer in status section (Copilot P1) - Regenerate docs/BACKLOG.md index (required check drift fix) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- B-0526: remove parent `depends_on: [B-0139]` → `depends_on: []` to keep atomic child independently pickable (Codex P1) - B-0526: fix `status: not-started` → `open` to match documented values (Copilot P1) - B-0526: fix `Task #321` → `#321` for proper GitHub auto-link (Copilot P1) - B-0139: update `last_updated` from 2026-05-08 → 2026-05-14 (Copilot P1) - B-0139: add explicit B-0526 pointer in status section (Copilot P1) - Regenerate docs/BACKLOG.md index (required check drift fix) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…3309) * chore(b-0139): decompose branch/worktree inventory to B-0526 * fix(backlog): address 5 review findings on B-0526 decomposition row - B-0526: remove parent `depends_on: [B-0139]` → `depends_on: []` to keep atomic child independently pickable (Codex P1) - B-0526: fix `status: not-started` → `open` to match documented values (Copilot P1) - B-0526: fix `Task #321` → `#321` for proper GitHub auto-link (Copilot P1) - B-0139: update `last_updated` from 2026-05-08 → 2026-05-14 (Copilot P1) - B-0139: add explicit B-0526 pointer in status section (Copilot P1) - Regenerate docs/BACKLOG.md index (required check drift fix) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(backlog): B-0526 add parent field, blank line MD032, B-0139 children list - Add parent: B-0139 to B-0526 frontmatter (resolves P0/P2 threads) - Add blank line before ordered list in B-0526 (fixes MD032 markdownlint CI failure) - Add B-0526 to B-0139 children list for decomposition tracking Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(b-0526): MD032 blank line before list + add B-0526 to B-0139 children - Add blank line before "Scope includes:" ordered list (markdownlint MD032) - Add B-0526 to B-0139 frontmatter children list for complete decomposition tracking Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
First cartel-detection primitive per Graph ADR #316. Power iteration + L2 normalize + Rayleigh quotient on symmetrized adjacency.
Load-bearing cartel-injection test: baseline 5-node sparse graph vs. baseline + K_4 clique (weight 10). Attacked lambda ≥ 5x baseline lambda. Detection signal in action.
Test invariants:
21 tests passing. Next: modularity + toy harness.
🤖 Generated with Claude Code