instar 1.3.819 → 1.3.821

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +498 -3
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/config/ConfigDefaults.d.ts.map +1 -1
  5. package/dist/config/ConfigDefaults.js +69 -0
  6. package/dist/config/ConfigDefaults.js.map +1 -1
  7. package/dist/core/BackupManager.d.ts +10 -0
  8. package/dist/core/BackupManager.d.ts.map +1 -1
  9. package/dist/core/BackupManager.js +16 -0
  10. package/dist/core/BackupManager.js.map +1 -1
  11. package/dist/core/BoundedJsonlAudit.d.ts +37 -0
  12. package/dist/core/BoundedJsonlAudit.d.ts.map +1 -0
  13. package/dist/core/BoundedJsonlAudit.js +87 -0
  14. package/dist/core/BoundedJsonlAudit.js.map +1 -0
  15. package/dist/core/JudgmentProvenanceLog.d.ts +144 -0
  16. package/dist/core/JudgmentProvenanceLog.d.ts.map +1 -0
  17. package/dist/core/JudgmentProvenanceLog.js +301 -0
  18. package/dist/core/JudgmentProvenanceLog.js.map +1 -0
  19. package/dist/core/MachineIdentity.d.ts.map +1 -1
  20. package/dist/core/MachineIdentity.js +2 -0
  21. package/dist/core/MachineIdentity.js.map +1 -1
  22. package/dist/core/OwnerDarkLadder.d.ts +108 -0
  23. package/dist/core/OwnerDarkLadder.d.ts.map +1 -0
  24. package/dist/core/OwnerDarkLadder.js +229 -0
  25. package/dist/core/OwnerDarkLadder.js.map +1 -0
  26. package/dist/core/PostUpdateMigrator.d.ts +10 -0
  27. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  28. package/dist/core/PostUpdateMigrator.js +106 -0
  29. package/dist/core/PostUpdateMigrator.js.map +1 -1
  30. package/dist/core/SpawnAdmission.d.ts +218 -0
  31. package/dist/core/SpawnAdmission.d.ts.map +1 -0
  32. package/dist/core/SpawnAdmission.js +440 -0
  33. package/dist/core/SpawnAdmission.js.map +1 -0
  34. package/dist/core/devGatedFeatures.d.ts.map +1 -1
  35. package/dist/core/devGatedFeatures.js +24 -0
  36. package/dist/core/devGatedFeatures.js.map +1 -1
  37. package/dist/core/machineCoherenceManifest.d.ts.map +1 -1
  38. package/dist/core/machineCoherenceManifest.js +30 -0
  39. package/dist/core/machineCoherenceManifest.js.map +1 -1
  40. package/dist/core/types.d.ts +87 -0
  41. package/dist/core/types.d.ts.map +1 -1
  42. package/dist/core/types.js.map +1 -1
  43. package/dist/monitoring/DuplicateSessionReconciler.d.ts +225 -0
  44. package/dist/monitoring/DuplicateSessionReconciler.d.ts.map +1 -0
  45. package/dist/monitoring/DuplicateSessionReconciler.js +544 -0
  46. package/dist/monitoring/DuplicateSessionReconciler.js.map +1 -0
  47. package/dist/monitoring/FailureAnalyzer.d.ts.map +1 -1
  48. package/dist/monitoring/FailureAnalyzer.js +40 -1
  49. package/dist/monitoring/FailureAnalyzer.js.map +1 -1
  50. package/dist/monitoring/FailureLedger.d.ts +9 -0
  51. package/dist/monitoring/FailureLedger.d.ts.map +1 -1
  52. package/dist/monitoring/FailureLedger.js +21 -4
  53. package/dist/monitoring/FailureLedger.js.map +1 -1
  54. package/dist/scaffold/templates.d.ts.map +1 -1
  55. package/dist/scaffold/templates.js +2 -1
  56. package/dist/scaffold/templates.js.map +1 -1
  57. package/dist/server/AgentServer.d.ts +5 -0
  58. package/dist/server/AgentServer.d.ts.map +1 -1
  59. package/dist/server/AgentServer.js +4 -0
  60. package/dist/server/AgentServer.js.map +1 -1
  61. package/dist/server/CapabilityIndex.d.ts.map +1 -1
  62. package/dist/server/CapabilityIndex.js +9 -0
  63. package/dist/server/CapabilityIndex.js.map +1 -1
  64. package/dist/server/fileRoutes.d.ts +16 -0
  65. package/dist/server/fileRoutes.d.ts.map +1 -1
  66. package/dist/server/fileRoutes.js +46 -0
  67. package/dist/server/fileRoutes.js.map +1 -1
  68. package/dist/server/routes.d.ts +7 -0
  69. package/dist/server/routes.d.ts.map +1 -1
  70. package/dist/server/routes.js +99 -0
  71. package/dist/server/routes.js.map +1 -1
  72. package/dist/testing/selfActionRegistry.d.ts.map +1 -1
  73. package/dist/testing/selfActionRegistry.js +89 -0
  74. package/dist/testing/selfActionRegistry.js.map +1 -1
  75. package/package.json +1 -1
  76. package/scripts/lib/self-action-detect.mjs +2 -0
  77. package/skills/instar-dev/templates/side-effects-artifact.md +8 -0
  78. package/skills/spec-converge/SKILL.md +1 -0
  79. package/skills/spec-converge/scripts/write-convergence-tag.mjs +71 -0
  80. package/src/data/builtin-manifest.json +65 -65
  81. package/src/scaffold/templates.ts +2 -1
  82. package/upgrades/1.3.820.md +25 -0
  83. package/upgrades/1.3.821.md +22 -0
  84. package/upgrades/side-effects/ownership-gated-spawn-increment-1.md +166 -0
  85. package/upgrades/side-effects/two-node-harness-increment2-entry-gate.md +64 -0
@@ -0,0 +1,166 @@
1
+ # Side-Effects Review — Ownership-Gated Spawn, Duplicate Reconciliation & Judgment-Within-Floors (Increment 1)
2
+
3
+ **Version / slug:** `ownership-gated-spawn-increment-1`
4
+ **Date:** `2026-07-11`
5
+ **Author:** `echo (instar-dev agent)`
6
+ **Second-pass reviewer:** `independent reviewer subagent (required — session lifecycle + gates)`
7
+
8
+ ## Summary of the change
9
+
10
+ Increment 1 of `docs/specs/ownership-gated-spawn-and-judgment-within-floors.md` (converged round 5, operator-approved 2026-07-10, all three §7 standards ratified). Adds the binding-verdict seam `src/core/SpawnAdmission.ts` (+ `resolveOwnershipSafe`) consulted at all five conversation-bound session-creating callsites (Telegram cold-spawn / two respawns, Slack inbound / recovery spawn in `src/commands/server.ts`); the owner-dark ladder rung-3 deterministic notice floor (`src/core/OwnerDarkLadder.ts`); the duplicate-session reconciler (`src/monitoring/DuplicateSessionReconciler.ts`) on a 60s lease-holder tick; the judgment-provenance log (`src/core/JudgmentProvenanceLog.ts`, machine-local `state/judgment-provenance/`); the net-new `NEVER_SERVED_PREFIXES` HTTP deny in `src/server/fileRoutes.ts`; three status/read routes (`GET /pool/duplicate-reconciler`, `GET /pool/ownership-view`, `GET /judgment-provenance`); four dev-gated flags (`ownershipGatedSpawn`, `duplicateReconciler`, `judgmentArbiters`, `commitmentCustodyTransfer`) registered in `DEV_GATED_FEATURES` + the three pool-behavior flags in `COHERENCE_CRITICAL_FLAGS`; the three ratified standards appended to `docs/STANDARDS-REGISTRY.md`; the §3.6 process hooks (spec-converge decision-point question + tag-writer structural refusal, side-effects template §4b question, `FailureRecord.judgmentCandidate` + analyzer cluster); three `PostUpdateMigrator` migrations (two skill-file patches + the gitignore patch); gitignore/backup exclusions for the provenance dir; agent-awareness template section; Capacity Safety registry entries; full three-tier tests incl. the burst-invariant E2E. **Everything ships dev-gated + dryRun (observe-only): no runtime behavior changes anywhere until deliberate flag flips that themselves require the durable substrate.**
11
+
12
+ ## Decision-point inventory
13
+
14
+ - `SpawnAdmission.admit()` (`src/core/SpawnAdmission.ts`) — **add** — may this machine create a session for this conversation (5-row deterministic admission table; enforce refusals gated on durable custody).
15
+ - `OwnerDarkLadder.handleOwnerDark()` (`src/core/OwnerDarkLadder.ts`) — **add** — notify-or-stay-silent for a dark-owner conversation (deterministic guards: liveness re-check, topic-history suppression, episode dedupe, cooldown).
16
+ - `DuplicateSessionReconciler.intendedOwner()` (`src/monitoring/DuplicateSessionReconciler.ts`) — **add** — which duplicate survives (evidence ladder pin → admissible epoch → live run → ESCALATE; every ambiguity escalates, never guesses).
17
+ - `validatePath` in `src/server/fileRoutes.ts` — **modify** — adds the hardcoded never-served deny (Layers 3b + 5e).
18
+ - `write-convergence-tag.mjs` — **modify** — refuses the convergence tag when `## Decision points touched` is missing/unclassified.
19
+ - `FailureAnalyzer.analyze()` — **modify** — adds the judgment-candidate cluster (signal-only; template recommendation).
20
+ - The five spawn callsites in `src/commands/server.ts` — **modify** — consult the seam before creating a session (pass-through in dry-run).
21
+ - `_ladderDryRunConsult` in `src/commands/server.ts` — **add** — the observe-stage soak consult: on an ALLOWED spawn whose verdict is wouldBlock+other-dark, journal the ladder's would-notice in dry-run mode (never sends; bounded by the ladder's own dedupe layers). Added resolving second-pass finding 2.
22
+
23
+ ---
24
+
25
+ ## 1. Over-block
26
+
27
+ **What legitimate inputs does this change reject that it shouldn't?**
28
+
29
+ In Increment 1: **nothing** — every admission decision returns `allow: true` outside enforce mode, and enforce mode is structurally unreachable (it requires `dryRun:false` AND the durable inbound queue live on the machine — the §3.1 item-6 admission-table invariant, encoded in `effectiveMode()` and unit-tested). Residual over-block risks AT the eventual enforce flip, designed against now:
30
+ - A legitimate spawn on a machine whose ownership registry read THROWS → row (e) fails **toward the spawn** (reachability wins), bounded by the code-constant breaker. Only a breaker-OPEN enforce state (≥5 consecutive / ≥8-in-10-min registry errors) degrades to the notice floor — a sustained registry outage, not a blip.
31
+ - A single-machine install or pool-dark agent short-circuits to allow with zero writes — byte-identical dispatch (tested).
32
+ - The file-routes deny (`NEVER_SERVED_PREFIXES`) rejects exactly one prefix (`state/judgment-provenance/`), evaluated pre- and post-realpath; a normal file under `state/` (e.g. `state/swap-ledger.jsonl`) is untouched (tested for no-over-block).
33
+ - The convergence-tag refusal applies to NEW specs only (grandfathering allowlist + the `*(none)*` escape for genuinely decision-free specs); an in-flight review past round 1 can be added to `GRANDFATHERED_SLUGS` by PR.
34
+
35
+ ## 2. Under-block
36
+
37
+ **What failure modes does this still miss?**
38
+
39
+ - **The fleet keeps today's behavior entirely** — dryRun everywhere means the 2026-07-10 incident CLASS still reproduces on the fleet until Increment 2 flips enforcement on the dev pool (by design: the staged rollout the operator approved; the dry-run soak is the evidence the flip requires).
40
+ - The reconciler cannot heal duplicates **wholly inside a network partition it cannot observe** (§3.2.1 partition honesty — rope-health alarms own that visibility gap).
41
+ - The seam consults at the five KNOWN conversation-bound creation callsites; a FUTURE session-creating callsite added without consulting the seam re-opens the gap — mitigated by the callsite-pin wiring test (fails if the literals move) and the Ownership-Gated Side Effects standard in the registry (review-time teeth), not by runtime interception.
42
+ - Registry-error episodes: duplicates minted during a sustained registry outage converge only after recovery (the §0 argued exemption — bounded: once per topic per episode + breaker).
43
+ - The rung-3 notice floor covers Telegram topics; a Slack conversation refused in enforce mode gets durable-queue custody where live, else a loud fail-open spawn — the Slack notice surface rides Increment 2 with the enforce flip it belongs to (the ladder records the episode journal-only for Slack today; enforce cannot engage in Increment 1 anywhere).
44
+
45
+ ## 3. Level-of-abstraction fit
46
+
47
+ The seam is deliberately at the **one place sessions get created** (the callsites), not inside the router (whose verdict was already correct and already ignored — the incident's exact lesson: adding intelligence to the router changes nothing until the verdict is binding at the effect site). The reconciler converges the **record** and reuses the existing gated closeout rather than adding a new killer (no parallel reap authority). The provenance log is a new primitive because none exists (`ResponseReviewDecisionLog` caps at 200 chars by design — verified). `NEVER_SERVED_PREFIXES` lives inside `validatePath` (the chokepoint all serving routes share) rather than per-route, with explicit guards only for the two paths that bypass the validator (link route; the WS4.4 proxy is structurally view-id-scoped and cannot address file paths — its coverage is the holder-side validator).
48
+
49
+ ## 4. Signal vs authority compliance
50
+
51
+ **Required reference:** docs/signal-vs-authority.md
52
+
53
+ - [x] Yes — but the logic is a deterministic floor in the documented exemption class.
54
+
55
+ The seam holds blocking authority (in enforce mode) with deterministic logic — this is signal-vs-authority's **sanctioned exemption class** ("hard-invariant validation" + "safety guards on irreversible actions"): the one-owner-per-conversation invariant over an enumerable 5-row domain, where a false block costs a resend notice and a false pass mints a duplicate with irreversible external side effects. The spec's §0 grounds this explicitly, and the ratified **Judgment Within Floors** standard sharpens it: the ENUMERABLE rows stay deterministic; the two genuinely fuzzy points (owner-dark timing J1, messy-evidence survivor J2) are declared judgment candidates whose arbiters arrive shadow-first in Increment 3 — until then their deterministic defaults (ladder timings; escalate-to-attention) run exclusively. Every ambiguous reconciler branch ESCALATES to the operator rather than guessing. The failure-analyzer addition and the §3.6 gate questions are pure signals (template text, review prompts); the tag-writer refusal is a structural presence check whose semantic authority stays with the reviewer.
56
+
57
+ ## 4b. Judgment-point check (Judgment Within Floors standard)
58
+
59
+ **Does this change add a static heuristic at a competing-signals decision point? If yes: why is it not a judgment point within a floor?**
60
+
61
+ No new static heuristic at a competing-signals point. The admission table rows are enumerable invariants (classified `invariant` in the spec's `## Decision points touched`, with justification). The two competing-signals points this flow contains (J1 owner-dark timing, J2 survivor under messy evidence) are classified `judgment-candidate` with declared floors; Increment 1 ships their deterministic defaults (which are ESCALATE/fixed-ladder, not guessing heuristics — "most recent user interaction" was deliberately rejected as a rule because the wrong copy gets the last message during this bug).
62
+
63
+ ## 5. Interactions
64
+
65
+ - **Shadowing:** the seam runs AFTER the router's verdict (and CONSUMES it via the TOCTOU guard — same message id — so the two cannot disagree). It does not shadow the G3 lease gate (which runs first at the cold-spawn callsite and returns before the seam on forward); both refusing is coherent (either alone suffices). The inbound-queue ordering gate and custody-ack short-circuit run before the seam unchanged.
66
+ - **Double-fire:** the reconciler defers any topic with an in-flight transfer/placement (`status transferring/placing`) and freezes during registry-error episodes — one authority in motion per topic. The rung-3 notice carries four independent dedupe layers (episode set, cooldown, pre-send liveness re-check, topic-history suppression) against split-brain double-voicing.
67
+ - **Races:** the reconciler probes fresh (5s budget) before any write; cache rows are never acted on. A CAS conflict escalates, never retries. The closeout is untouched — its own confirm-ticks/veto-breaker/guards still decide every close.
68
+ - **Feedback loops:** the reconciler's own convergence writes could re-trigger detection — bounded by 3 attempts/episode + the P19 breaker (3 episodes/24h clamps the topic, counting record FLIPS and echoed-but-unhealed episodes; transfer-traceable episodes excluded from the clamp but surfaced at volume). The Capacity Safety registry entries model both loops under sustained pressure and the convergence ratchet passes.
69
+ - **Existing tests:** the ConfigDefaults line-map golden test and the coherence-manifest membership test pass unmodified against the new entries (37 + 45 green) — no hand-update turned out to be needed (corrected per second-pass finding 11).
70
+ - **Dry-run soak consult (added post-second-pass, resolving its finding 2):** the shared `_ladderDryRunConsult` helper (src/commands/server.ts) rides all five callsites' ALLOW path: when the seam observes a would-refuse on a DARK owner, the ladder journals the would-notice in `mode: 'dry-run'` (never sends). Without it the ladder would sit dark through the whole observe stage and the enforce flip would arrive with zero ladder soak data. Volume is bounded by the ladder's own episode-dedupe/cooldown layers; the burst E2E asserts exactly ONE would-notice row per (topic, episode) under a 500-message burst.
71
+ - **Breaker-bounds asymmetry (disclosed):** the SEAM's error-arm breaker bounds are code constants (`ERROR_ARM_CONSTANTS`, §3.1 row e — spec-mandated). The RECONCILER's P19 breaker bounds (`breakerThreshold`/`breakerWindowMs`) ride `ReconcilerConfigView` from config — the spec's code-constant mandate is scoped to the seam's error arm; the reconciler's clamp stays tunable like other P19 breakers. Loosening it is a config change an operator can make; the dry-run/dev gates still bound the blast radius in Increment 1.
72
+
73
+ ## 6. External surfaces
74
+
75
+ - **Other agents / install base:** all four flags ride the dev-agent gate — fleet agents get dormant code + config defaults via migration, zero behavior change. The three migrations patch agent-installed files (two skill files by full-copy-when-unmodified; gitignore by idempotent append).
76
+ - **External systems:** no new egress. The reconciler's discovery/probes/echo reads ride the EXISTING authenticated peer HTTP surfaces (`/sessions`, `/autonomous/sessions`, the new Bearer-authed `/pool/ownership-view`) at bounded cadence (60s tick, 5s per-probe budgets, per-tick caps).
77
+ - **Persistent state:** new machine-local artifacts — `state/judgment-provenance/*.jsonl` (0700/0600, gitignored via ensureGitignore + migration, backup-excluded via `NEVER_BACKUP_PATH_SEGMENTS`, 14-day retention, never HTTP-served raw) and two bounded audit logs (`logs/owner-dark-ladder.jsonl`, `logs/duplicate-reconciler.jsonl`, 5MB rotation via SafeFsExecutor). PLUS one schema change on an EXISTING durable store (per second-pass finding 9): `failure_records` gains the `judgment_candidate INTEGER NOT NULL DEFAULT 0` column via idempotent `ALTER TABLE` in the FailureLedger SCHEMA loop (duplicate-column re-runs swallowed, anything else rethrown — the TokenLedger precedent; round-trip + re-open tested in `tests/unit/FailureAnalyzer-judgment-candidate.test.ts`). Rollback note: a reverted binary leaves the extra column in place — additive, DEFAULT 0, ignored by old readers, harmless.
78
+ - **New config surface (per second-pass finding 10):** the top-level `standards.selfHealBeforeNotify.recoverableLatencyCeiling: 300` block lands in ConfigDefaults + types with NO runtime consumer in Increment 1 — deliberate per the spec's §3.8 authority clause (the key lands at the three-standards-enforcement spec's converged value; the watchers' status-route observability lines reference the SAME 300s bound as code today, and the key becomes load-bearing with the Increment-2 enforcement preconditions). An inert default: absent/present changes no Increment-1 behavior.
79
+ - **Timing/runtime conditions:** the seam adds one in-memory map lookup + (when pool active) one cached registry read per spawn decision — no synchronous durable reads on the inbound path (§3.1 item 1). The provenance writer is async-buffered.
80
+ - **Operator surface (Mobile-Complete Operator Actions):** no operator-facing ACTION is added (all new routes are read-only status/observability; escalations land on the existing Attention surface with existing ack flows). "No operator-facing actions" applies.
81
+
82
+ ## 6b. Operator-surface quality
83
+
84
+ No operator surface — not applicable (read-only JSON status routes; no dashboard renderer/approval/grant/revoke/secret-drop file touched).
85
+
86
+ ## 7. Multi-machine posture (Cross-Machine Coherence)
87
+
88
+ Multi-machine BY SUBJECT; per-surface postures per the spec's Standard-A table:
89
+ - **SpawnAdmission verdicts:** unified (derived per-call from the replicated registry's cached view; no new durable state).
90
+ - **Ownership records the reconciler writes:** unified — the EXISTING `SessionOwnershipRegistry` CAS + journal replication; the §3.2.0 substrate gate refuses to arm on the fleet-default in-memory store (loud `substrate-not-ready`, never silent).
91
+ - **Provenance full bodies:** machine-local write, proxied-on-read (`machine-local-justification: physical-credential-locality` is NOT claimed — the correct posture per the spec is machine-local-full/HTTP-redacted: full rows carry chokepoint-redacted-but-sensitive decision context bound to the deciding machine's disk by the at-rest honesty contract; the UNIFIED read is the redacted `?scope=pool` merge).
92
+ - **Owner-dark hold state:** machine-local BY DESIGN (`machine-local-justification: physical-credential-locality` — in-flight delivery state of THIS machine's live adapter socket, per the spec's table).
93
+ - **Breaker counters:** in-module per-lease-holder state in Increment 1 (the reconciler runs ONLY on the lease holder, so the counters live where the only writer lives); the replicated-store ride + receive-side clamp land with the Increment-2 enforcement flip they protect — the clamp function (`clampBreakerRow`) ships + is unit-tested NOW.
94
+ - **User-facing notices:** one-voice-gated by the four dedupe layers incl. the topic-history suppression that covers split-brain (§3.3.3); delivered on the deterministic G1 path with the spec-declared owner-dark exception to speaker election.
95
+ - **Flag skew:** the three pool-behavior flags joined `COHERENCE_CRITICAL_FLAGS` (the machine-coherence guard alarms on a split pool); `judgmentArbiters` carries an explicit manifest exclusion with the reason (floors' static defaults make a mixed pool safe).
96
+ - No generated URLs.
97
+
98
+ ## 8. Rollback cost
99
+
100
+ Pure code + config-defaults change, everything dev-gated dryRun: **revert the PR, ship as next patch.** No data migration to unwind (the provenance dir and audit logs are inert observability files; the gitignore/backup entries are safe to leave). Existing agents that took the skill-file migrations keep the new review questions (harmless prose additions; reverting them is another migration if ever needed). The three standards in the registry are operator-RATIFIED text — reverting those is an operator decision, not a rollback mechanic. No user-visible regression in any rollback window (nothing user-visible shipped ON).
101
+
102
+ ---
103
+
104
+ ## Conclusion
105
+
106
+ The review confirmed the Increment-1 posture is strictly observe-only with three independent belts (flag dev-gated; dryRun default; enforce structurally requires durable custody), that the seam's fail direction under registry failure is toward reachability with code-constant bounds, and that the reconciler adds no new kill authority. Two design notes surfaced and were resolved during the review: (a) the WS4.4 proxy needs no in-proxy deny because it is structurally view-id-scoped — coverage is the holder-side validator, and the wiring test pins the validator's 403; (b) breaker-counter replication is deliberately staged with the enforcement flip it protects, with the receive-clamp shipped and tested now. Clear to ship as Increment 1; the enforce flip (Increment 2) inherits its own preconditions (durable queue live, hold policy live, stale-owner-release live, two-node CI harness green) per the spec's rollout ladder.
107
+
108
+ ---
109
+
110
+ ## Second-pass review (required — session lifecycle + spawn/dispatch gates)
111
+
112
+ **Reviewer:** independent reviewer subagent — completed; full verdict + findings in the `## Second-pass review (independent reviewer)` section at the end of this artifact.
113
+ **Independent read of the artifact:** done — every material claim grounded against the working-tree diff; new unit/wiring/ratchet tests executed (171/171 + 45 + 37 green).
114
+
115
+ ---
116
+
117
+ ## Evidence pointers
118
+
119
+ - Unit: `tests/unit/SpawnAdmission.test.ts`, `tests/unit/OwnerDarkLadder.test.ts`, `tests/unit/JudgmentProvenanceLog.test.ts`, `tests/unit/BoundedJsonlAudit.test.ts`, `tests/unit/DuplicateSessionReconciler.test.ts`, `tests/unit/write-convergence-tag-decision-points.test.ts`, `tests/unit/FailureAnalyzer-judgment-candidate.test.ts`
120
+ - Wiring: `tests/unit/fileRoutes-never-served.test.ts` (403s incl. symlink evasion + edit paths), `tests/unit/spawn-admission-callsite-pins.test.ts`, `tests/unit/BackupManager-never-backup.test.ts`, `tests/unit/PostUpdateMigrator-judgment-floors.test.ts`
121
+ - Integration/E2E: `tests/integration/duplicate-reconciler-routes.test.ts`, `tests/e2e/ownership-gated-spawn-burst-invariant.test.ts` (N inbound → ZERO local sessions + ONE notice per topic-episode; observe-mode pass-through parity), `tests/e2e/ownership-gated-spawn-alive.test.ts` (Tier-3 feature-alive over the REAL AgentServer — real auth middleware 401/403, redaction-over-the-wire with a token-shaped probe, dark-posture 503s, never-served deny)
122
+ - Ratchets: `tests/unit/self-action-convergence.test.ts` (owner-dark-notice + duplicate-converge-write controllers), `tests/unit/machine-coherence-manifest.test.ts`, `tests/unit/lint-dev-agent-dark-gate.test.ts`, `scripts/lint-self-heal-fields.js` (three §3.8 blocks pass)
123
+
124
+ ---
125
+
126
+ ## Class-Closure Declaration (display-only mirror)
127
+
128
+ - **`defectClass`**: `unbounded-self-action` (this change ADDS self-triggered controllers: the duplicate reconciler tick loop, the owner-dark notice emitter, the spawn-admission error-arm attention raiser).
129
+ - **`closure`**: `guard`
130
+ - **`guardEvidence`**: `{enforcementType: ratchet, citation: tests/unit/self-action-convergence.test.ts (SELF_ACTION_CONTROLLERS entries 'owner-dark-notice' + 'duplicate-converge-write'), howCaught: both controllers are driven under sustained pressure fixtures — the ladder converges to ONE notice per (topic, episode) with the episode set as the latch and owner-recovery as the only re-arm (horizon-independent: 2N ticks emit no more than N), and the reconciler converges to ≤3 attempts × 3 episodes then the P19 breaker clamps the topic to silence + ONE attention item — an unbounded repair/notice loop fails the ratchet's horizon-independence and bound assertions before it can ship.}`
131
+
132
+ ---
133
+
134
+ ## Second-pass review (independent reviewer)
135
+
136
+ **Reviewer:** independent subagent, 2026-07-11
137
+ **Verdict:** Concur with the review *(updated 2026-07-11 after independent re-verification — initial verdict was "Concern raised" on two items, both since resolved; original findings retained below with resolution notes)*
138
+
139
+ **Resolution re-verification (second pass, round 2):**
140
+ - **Concern 1 RESOLVED.** All three previously-missing evidence tests now exist and pass, re-run by this reviewer: `tests/unit/write-convergence-tag-decision-points.test.ts` (20 tests — `findDecisionPointGaps` parser table-/blockquote-aware, `*(none)*` escape, PLUS end-to-end refusal/stamp via `execFileSync`, PLUS a pin that `GRANDFATHERED_SLUGS` ships EMPTY), `tests/unit/FailureAnalyzer-judgment-candidate.test.ts` (7 tests — persistence round-trip incl. unfiled→undefined, idempotent ALTER re-open, cross-category cluster, diversity gate, no flag inference, idempotent upsert), and the Tier-3 alive test as `tests/e2e/ownership-gated-spawn-alive.test.ts` (real AgentServer, REAL auth middleware 401/403, redaction-over-the-wire probe, dark 503s) — the Evidence pointers now cite the real filename. 48/48 green across the resolution suites + both E2Es + callsite pins on re-run.
141
+ - **Concern 2 RESOLVED in production (the right direction).** New `_ladderDryRunConsult` helper (src/commands/server.ts:846-860): guard `!_ownerDarkLadder || !d.allow || !d.wouldBlock` + `ownership.kind === 'other-dark'` — exactly the burst harness's condition; invoked on the ALLOW path of all five callsites (once inside `admitLocalSpawn` covering the 3 Telegram sites at :2618, slack-inbound :8301, slack-recovery :10869, each additionally inside the existing try/catch). Cannot throw onto the spawn path: async fn (no synchronous throw) called `void …().catch(() => {})`, and `handleOwnerDark` mode `'dry-run'` journals only — the send branch is structurally behind `mode === 'enforce'` (OwnerDarkLadder.ts:190-196). The §2 Slack wording this reviewer flagged is now accurate (the ladder does record journal-only rows for Slack via topicId null).
142
+ - **Noted items 9/10/11 folded and verified:** the FailureLedger `ALTER TABLE judgment_candidate` disclosed under §6 with the rollback note; the consumer-less `standards.selfHealBeforeNotify.recoverableLatencyCeiling` block disclosed under §6; the §5 "updated by hand" claim corrected to "pass unmodified"; the breaker-bounds asymmetry disclosed under §5; `_ladderDryRunConsult` added to the Decision-point inventory. Also re-verified since round 1: `casConverge` still pairs `emitPlacement('reconcile')`; `DuplicateSessionReconciler.test.ts` (51 tests) green.
143
+
144
+ Original findings (round 1 — verified against the working-tree diff; note the tree was being written concurrently — `DuplicateSessionReconciler.test.ts` and the burst-invariant E2E landed DURING this review and were verified; findings 7-8 were the two concerns, now resolved as above):
145
+
146
+ 1. **Enforce structurally unreachable — TRUE.** `src/core/SpawnAdmission.ts:275-279` (`effectiveMode()`): enforce requires `enabled && !dryRun && durableCustodyLive()`. ConfigDefaults ships `dryRun:true` with `enabled` omitted (dev gate); wiring reads `dryRun: ogsCfg.dryRun !== false` and `durableCustodyLive: () => !!_inboundQueue` (src/commands/server.ts). `decide()` returns `allow:true` in dry-run for every blocking row; the error arm allows unless breaker-open AND enforce. 35 unit tests pass.
147
+ 2. **Five callsites wired, crash-proof — TRUE.** `admitLocalSpawn('telegram-cold-spawn'/'telegram-respawn-context-exhausted'/'telegram-respawn-dead')` each `if (!…) return;`, plus the slack-inbound and slack-recovery inline blocks — every one wrapped in try/catch that FAILS OPEN to the spawn on a seam throw. Seam-side observability deps are non-throwing (`BoundedJsonlAudit.append` swallows into its promise chain; `journalDecision`/`provenanceRow`/`raiseErrorEpisodeAttention` each try/catch). The callsite-pins test (8 tests, index-ordered) enforces the wiring; 171/171 across the five load-bearing suites I executed.
148
+ 3. **NEVER_SERVED_PREFIXES — TRUE, no over-block.** Pre-realpath (Layer 3b) AND post-realpath (Layer 5e) inside `validatePath`, explicit deny on the link route (which bypasses the validator), and `isNeverEditable` consults `isNeverServed` (serve-deny ⇒ edit-deny). Exactly one prefix + exact-dir match; sibling `state/` files unaffected (14 tests pass, incl. symlink evasion).
149
+ 4. **Reconciler — TRUE with one nuance.** dryRun lands no CAS (`would-converge` journal row, episode reset); every ambiguity escalates (both-live-runs, rule2/rule3 contradiction, equal-epoch tie, cas-refused, dead-target owner); `casConverge` pairs `emitPlacement(sk, r, 'reconcile', prevOwner)` and `emitPlacement` internally no-ops the journal emit unless `r.ok` ('reconcile' pre-exists in the `PlacementReason` union); substrate gate requires `durableOwnershipOn && _replicationOn` — refuses the fleet-default in-memory store. 51 unit tests pass. **Nuance:** only the per-episode 3-attempt cap (DuplicateSessionReconciler.ts:311) is a code constant; `breakerThreshold`/`breakerWindowMs`/per-tick caps are CONFIG-tunable (`SessionPoolConfig.duplicateReconciler`) — unlike the seam's `ERROR_ARM_CONSTANTS`. The artifact doesn't claim otherwise, but given the config-edit-removes-safety-bound lesson the seam's own header cites, the asymmetry is worth an explicit line (or a follow-up hardening).
150
+ 5. **Provenance log — TRUE.** 0700 dir + chmod re-assert, 0600 appends, 64KB row clamp with skeleton degeneracy, retention via `SafeFsExecutor.safeUnlink`, `readRedacted` omits `contextFull` by destructuring + write-time credential scrub; `GET /judgment-provenance` serves `readRedacted` only, pool-scope peer rows clamped at 8KB each; gitignored twice (MachineIdentity `GITIGNORE_ENTRIES` + the idempotent migration) and backup-excluded via segment-matched `NEVER_BACKUP_PATH_SEGMENTS`.
151
+ 6. **Signal-vs-authority / multi-machine / rollback — TRUE.** Nothing added holds blocking authority TODAY (dry-run everywhere; enforce triple-gated); the exemption-class argument matches `docs/signal-vs-authority.md:73-76`; the tag-writer refusal is a deterministic presence check with semantic authority left to the reviewer (and an EMPTY `GRANDFATHERED_SLUGS` — fine, but note it therefore applies to every spec immediately, not "NEW specs only" as §1 words it). The three pool-behavior flags are in `COHERENCE_CRITICAL_FLAGS` + the `judgmentArbiters` exclusion with reason; postures match §7. Rollback = revert-PR is real (all dark). The integration test boots a REAL AgentServer with live auth middleware and asserts 401 — the auth-bypass test lesson applied.
152
+ 7. **CONCERN — evidence-pointer completeness.** *(RESOLVED — see resolution block above: all three tests now exist and pass, re-run independently.)* Three cited tests absent at review close (listed in the verdict). Consequence today: the `write-convergence-tag.mjs` decision-point gate and the `FailureAnalyzer` judgment-candidate cluster ship with no coverage, and the Testing-Integrity Tier-3 "alive" test for the reconciler routes is missing. Not a code-safety issue (everything observed is observe-only) but a false evidence claim if committed as-is.
153
+ 8. **CONCERN (minor) — burst-E2E harness-parity misstatement.** *(RESOLVED — production now performs the dry-run ladder consult via `_ladderDryRunConsult` on all five callsites; guard + non-throwing contract re-verified above.)* `tests/e2e/ownership-gated-spawn-burst-invariant.test.ts:80-90` invokes `ladder.handleOwnerDark(..., mode:'dry-run')` on a dry-run would-block and comments "Observe-mode parity with production"; line 160 asserts the resulting `would-notice` row. Production `admitLocalSpawn` invoked the ladder ONLY on refusal (enforce-mode; `mode:'enforce'` hardcoded) — in production dry-run the ladder never ran and only the seam's would-block journal rows landed. Safe direction, but the asserted soak telemetry didn't exist in production; resolved by adding the dry-run ladder consult to the wiring. (Same root: artifact §2's "the ladder records the episode journal-only for Slack today" was an overstatement at round 1; it is accurate now.)
154
+ 9. **Missed side effect (noted, not blocking): FailureLedger SQLite schema migration.** `ALTER TABLE failure_records ADD COLUMN judgment_candidate INTEGER NOT NULL DEFAULT 0` with a duplicate-column swallow in the constructor exec loop — a persistent-state change §6 ("new machine-local artifacts only") doesn't list, and §8's "no data migration to unwind" glosses. Additive, DEFAULT-0, idempotent, revert-safe — but it belongs in the inventory.
155
+ 10. **Missed side effect (noted, not blocking): inert config surface.** `standards.selfHealBeforeNotify.recoverableLatencyCeiling: 300` landed in ConfigDefaults + types with NO code consumer anywhere in src/ (only the spec-converge SKILL prose references the key). Deliberate per the §3.8 authority clause, but the artifact doesn't mention the new top-level `standards` config block.
156
+ 11. **Imprecision (immaterial): §5 claims the ConfigDefaults line-map golden test and the coherence-manifest membership test "were updated by hand" — neither file is in the diff; both pass as-is (37 + 21 tests green), so no action needed, but the claim is inaccurate as written.
157
+
158
+ ---
159
+
160
+ ## Post-push CI-green addendum (2026-07-11)
161
+
162
+ The first CI round surfaced seven failing files, all interaction fallout of this change — none behavioral. Fixed in the follow-up batch shipping with this addendum: (a) the no-silent-fallbacks ratchet flagged the 13 new catch blocks — each now carries an in-block `@silent-fallback-ok` declaring its LOUD path (journal row / escalation / deferral / status counter); none were silent swallows, all lacked the declaration; (b) three tag-writer test fixtures gained the now-required `## Decision points touched — *(none)*` section (the new gate applies to every stamped spec, fixtures included); (c) two source-scan wiring tests' anchors/windows updated for the seam's insertions (respawnSessionFresh window 7000→9000; the dispatch's multi-line signature + verdict arg); (d) `Duplicate-Session Prevention` tracked in the CLAUDE.md-parity guard's shared-function list. No runtime behavior change in the batch.
163
+
164
+ ## Two-node-harness finding addendum (2026-07-11, pre-merge)
165
+
166
+ Building the Increment-2 entry-gate harness (real FSM, real HTTP, un-stubbed replication) surfaced a core-correctness gap the mocked unit tier could not see: in the 2026-07-10 incident's OWN shape — a bootleg spawn that duplicated the SESSION without ever touching the ownership RECORD — the reconciler's convergence CAS (`claim` on an active, already-correct, self-owned record) is refused by the ownership FSM by design (`claim-out-of-sequence`; the FSM comment even says a self-claim "masks a reconciler bug"), so the reconciler ESCALATED to the operator instead of self-healing. Fix (in `reconcileOne`, before the CAS): when the admissible SELF view already names the intended owner, skip the write entirely (no epoch burn, honoring the FSM's own principle), journal `record-already-converged`, and open the peer-echo window — the missing piece in this shape is peer replication/materialization, and the echo-confirmed path arms the existing closeout exactly as before. Both directions unit-tested (skip fires only on an admissible self view naming the verdict owner; a differing or inadmissible view still runs the CAS). No new side-effect surface: the change strictly REMOVES a write in a case where the write was always refused.
@@ -0,0 +1,64 @@
1
+ # Side-Effects Review — Two-Node Replication Harness (Increment-2 Entry Gate)
2
+
3
+ **Version / slug:** `two-node-harness-increment2-entry-gate`
4
+ **Date:** `2026-07-11`
5
+ **Author:** `echo (instar-dev agent)`
6
+ **Second-pass reviewer:** `not required (tests + test-support only; no decision point, no runtime surface)`
7
+
8
+ ## Summary of the change
9
+
10
+ Implements the Increment-2 entry-gate deliverable of `docs/specs/ownership-gated-spawn-and-judgment-within-floors.md` (§4 line 242: "Entry gate: the two-node replication harness (§5) green in CI"; §5 line 261: the L7 evidence contract). Two new test files only: `tests/support/twoNodeOwnershipHarness.ts` (the reusable two-node factory — durable ownership substrate, un-stubbed signed journal-sync replication, real AgentServer per node) and `tests/e2e/duplicate-reconciliation-two-node.test.ts` (the entry-gate lifecycle + the §5 delayed-replay and partition-formed cases + three spec-anchored `it.todo` scenarios). Plus this artifact, the ELI16 companion, and an internal-only release fragment. **Zero `src/**` changes.**
11
+
12
+ ## Decision-point inventory
13
+
14
+ *(none — no runtime decision point is added or modified; the harness exercises existing ones)*
15
+
16
+ ## 1. Over-block
17
+
18
+ Nothing at runtime (no runtime surface). In CI: the new E2E becomes a required-passing test — a future change that breaks the two-machine heal fails CI. That is the deliverable, not a side effect: the spec makes this test THE Increment-2 entry gate.
19
+
20
+ ## 2. Under-block
21
+
22
+ - The harness runs two nodes IN ONE PROCESS over loopback — partitions are modeled as WITHHELD replication (the §5 partition-formed and delayed-replay cases), not as packet loss/timeout dynamics; clock skew and cross-host filesystem differences are not modeled. The spec's §3.0 consistency contract owns those honesty bounds.
23
+ - The 2b custody-transfer scenario and the terminate-time-probe scenario are `it.todo` — their mechanics are deliberately NOT built in Increment 1/this PR (Increment 2b's own build); the todos carry the spec anchors so they cannot be silently forgotten.
24
+ - The closeout leg asserts the ARMING predicate (the peer's own view says owner-elsewhere) and simulates the close; the full sweeper-close leg lands with the `duplicate-reconciled` reap-reason extension (Increment 2).
25
+
26
+ ## 3. Level-of-abstraction fit
27
+
28
+ Reuses the three existing proven patterns (journal-sync-roundtrip's replication hop, mesh-failover's two-server shape, the alive-test's real-AgentServer boot) rather than inventing a parallel harness idiom. The node factory lives in `tests/support/` alongside the existing fixture module.
29
+
30
+ ## 4. Signal vs authority compliance
31
+
32
+ **Required reference:** docs/signal-vs-authority.md — Not applicable at runtime (no new detector or authority). In-CI authority (a failing test blocks merges) is the standard test-suite contract.
33
+
34
+ ## 5. Interactions
35
+
36
+ - The E2E rides the existing `e2e` CI job (vitest include already covers `tests/e2e/**`) — no workflow changes, no new CI lanes.
37
+ - The harness binds ephemeral loopback ports (port 0) and tmpdir state — no interaction with the host agent's server, state, or config.
38
+ - Discovered interaction (already resolved upstream): building this harness surfaced the record-already-correct FSM refusal (claim-out-of-sequence → escalate-instead-of-heal) — fixed on the Increment-1 PR as the `record-already-converged` skip and unit-tested there; this E2E now proves that fix end-to-end.
39
+
40
+ ## 6. External surfaces
41
+
42
+ None. No egress (loopback only), no persistent state outside vitest tmpdirs (SafeFsExecutor teardown), no operator surface, no agent-visible capability (hence the internal-only release-note lane).
43
+
44
+ ## 6b. Operator-surface quality
45
+
46
+ No operator surface — not applicable.
47
+
48
+ ## 7. Multi-machine posture (Cross-Machine Coherence)
49
+
50
+ The change IS the multi-machine test substrate. It runs no agent, replicates no store of its own, and creates no cross-machine surface; it simulates two machines inside one test process to verify the production replication contract (journal → signed envelope → applier → materialized peer view).
51
+
52
+ ## 8. Rollback cost
53
+
54
+ Delete the two test files (plus docs). Nothing depends on them at runtime. The only cost of rollback is losing the Increment-2 entry gate's objective checkability.
55
+
56
+ ## Rollout-ladder compliance (§4 hard prohibitions)
57
+
58
+ - NO flag flips: `ownershipGatedSpawn` / `duplicateReconciler` / `judgmentArbiters` / `commitmentCustodyTransfer` untouched; `inboundQueue` / `holdForStability` / stale-owner-release untouched (their own features' graduation decisions, per §4 "inherit-and-stall").
59
+ - NO `provenance.deterministicSampling` change (that is an Increment-2-ENTRY action, riding the actual enforce-flip PR).
60
+ - In-test enforce-mode construction of the reconciler is test construction inside a sandbox, not a rollout-ladder flip (the Increment-1 burst-invariant E2E precedent).
61
+
62
+ ## Conclusion
63
+
64
+ Tests + test-support only; the risk surface is CI-time, which is the point. Clear to ship as its own PR once Increment 1 merges (it depends on the Increment-1 modules).