kairos-chain 3.35.0 → 3.39.0

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 (23) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +129 -0
  3. data/bin/kairos-chain +80 -0
  4. data/lib/kairos_mcp/version.rb +1 -1
  5. data/templates/knowledge/design_to_implementation_workflow/design_to_implementation_workflow.md +5 -0
  6. data/templates/knowledge/multi_llm_review_workflow/multi_llm_review_workflow.md +181 -2
  7. data/templates/skillsets/synoptis/config/synoptis.yml +10 -0
  8. data/templates/skillsets/synoptis/lib/synoptis/constitutive/attestation_chain.rb +53 -3
  9. data/templates/skillsets/synoptis/lib/synoptis/constitutive/content_attestation_entry.rb +17 -4
  10. data/templates/skillsets/synoptis/lib/synoptis/constitutive/proposal_criterion.rb +11 -3
  11. data/templates/skillsets/synoptis/lib/synoptis/constitutive/revocation_withdrawal_entry.rb +60 -0
  12. data/templates/skillsets/synoptis/lib/synoptis/constitutive/subject_ref.rb +43 -6
  13. data/templates/skillsets/synoptis/lib/synoptis/tool_helpers.rb +14 -1
  14. data/templates/skillsets/synoptis/lib/synoptis.rb +1 -0
  15. data/templates/skillsets/synoptis/skillset.json +3 -1
  16. data/templates/skillsets/synoptis/test/test_l2_constitutive_slice1.rb +4 -1
  17. data/templates/skillsets/synoptis/test/test_l2_constitutive_slice2.rb +121 -0
  18. data/templates/skillsets/synoptis/test/test_l2_constitutive_slice3.rb +117 -0
  19. data/templates/skillsets/synoptis/tools/l2_attestation_commit.rb +68 -20
  20. data/templates/skillsets/synoptis/tools/l2_attestation_revoke.rb +104 -0
  21. data/templates/skillsets/synoptis/tools/l2_attestation_scan.rb +8 -4
  22. data/templates/skillsets/synoptis/tools/l2_attestation_view.rb +64 -0
  23. metadata +6 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 790cf38c2263e065c856211e0dce54a56a4e9599b2b028dbd3335405f78d069f
4
- data.tar.gz: 3a568dc9d900c4aefc24572657043c82d94ea62027a349f6c3e20094d778cbb7
3
+ metadata.gz: 0da61a60c3ecd5e32e85052668638f65522376949dc5577a54ab5db754e7118a
4
+ data.tar.gz: fe8f12657d7032c9047368372dd6e49bc733e83804a55c83717608980d0017bc
5
5
  SHA512:
6
- metadata.gz: a3d7d364ecfe3512de1ca75375eefe890002b4c228f7dd3245ea00277abe1552030261a3916ef27ec3c1e968381646ac73a0811672016a8d16d6fab282954c31
7
- data.tar.gz: 1bc3370f34c14841d0b57c471ff9e71185521bfe36b583c788732f6513d453c619a07e53070bb6e4d674b6b3b2ae4ec716663f53247d65e9a380a968e942752c
6
+ metadata.gz: 6e466478fd4e87509d7b4efa1218813fba0f395ea2296852a69b4f2190070ddddf59b2a8961f4b1a34d71ad4d1666716df105ad609f21e6ac9f91e228a58dbf8
7
+ data.tar.gz: 4cf41c84ff411cb3e18de05683343eb3f84a3281c7f93461ec90c6ea4806919ebf58ae70694e0e629248189411b6b815d764cb3e10f6956d502f5222fc60cc46
data/CHANGELOG.md CHANGED
@@ -4,6 +4,135 @@ All notable changes to the `kairos-chain` gem will be documented in this file.
4
4
 
5
5
  This project follows [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## [3.39.0] - 2026-07-05
8
+
9
+ ### Added — L1 `multi_llm_review_workflow` v3.5 → v3.6: Step 0.25 Unknowns Pass
10
+
11
+ Adds a pre-draft unknown-discovery discipline to the multi-LLM review workflow,
12
+ adapted from Thariq Shihipar's "A Field Guide to Fable: Finding Your Unknowns"
13
+ (2026-07-03), techniques ① blindspot pass and ③ one-question-at-a-time interview.
14
+ Bundled L1 knowledge only — no code paths changed.
15
+
16
+ - **Step 0.25 (pre-draft, qualifying reviews only)**: before drafting a design or
17
+ knowledge/documentation artifact routed to full multi-LLM review, the orchestrator
18
+ enumerates its blind-spot questions (without self-answering) and interviews the human
19
+ one question at a time, then triages every surfaced unknown to Resolved / Declared /
20
+ Draft-deferred. Moves unknown discovery from the expensive channel (review rounds) to
21
+ the cheap channel (pre-draft dialogue).
22
+ - **Five invariants INV-U1–U5**: front-load gate (no dispatch before the pass);
23
+ human gate (no orchestrator self-answering); constitutive recording (unconditional L2
24
+ record, incl. zero-result); bounded demotion of declared unknowns (provenance bound +
25
+ inverted default, closing a laundering channel); human-only classification authority.
26
+ - **Unattended (autonomous loop) clause**: fail-closed — every surfaced unknown is
27
+ judgment-shaped by default and stops the run; only a human-authored mandate may
28
+ pre-classify categories as non-judgment-shaped, and unattended declarations gain no
29
+ reviewer-demotion power until human-ratified. Mandate expressiveness, ratification
30
+ protocol, and the pending-question queue are deferred to the Autonomous Growth Loop
31
+ guard track.
32
+ - Three-location L1 sync (instance `.kairos/knowledge/`, gem `knowledge/`, gem
33
+ `templates/knowledge/`); change recorded to blockchain (hash a74a11b7…). One-line
34
+ cross-reference added to `design_to_implementation_workflow`.
35
+ - Designed via its own 3-round self-referential multi-LLM review (R1 REVISE 2/6 →
36
+ R2 REVISE 2/6 → R3 4/6 APPROVE; design record
37
+ `docs/drafts/multi_llm_review_unknowns_pass_v0.3.1_FROZEN.md`).
38
+
39
+ ## [3.38.0] - 2026-07-05
40
+
41
+ ### Added — Synoptis L2 attestation: ACT-5 trigger sources + scan CLI (Slice 3b/3c)
42
+
43
+ Makes the ACT-5 trigger point distinguishable and invocable from a session-end
44
+ Stop hook, so firing + trigger-record logging can be guaranteed rather than relying
45
+ on the orchestrator remembering to run the tool.
46
+
47
+ - **Trigger source (3b)**: `l2_attestation_scan` gains a `trigger_source` argument
48
+ (`manual` / `orchestrator_session_end` / `session_end_hook`), recorded in the trigger
49
+ record (`AttestationChain#append_trigger(source:)`). ACT-5's "at least one defined
50
+ trigger point" is now distinguishable in the operational log, and the tool documents
51
+ its two auto-firing paths (orchestrator-at-session-end for delivery+approval; Stop hook
52
+ for a mechanical liveness guarantee).
53
+ - **Scan CLI (3c)**: new `kairos-chain attestation scan [--session SID] [--source SRC]
54
+ [--data-dir DIR]`. It loads the constitutive criterion, proposes a session's
55
+ judgment-bearing contexts, and appends a trigger record — propose-only, never attests
56
+ (ACT-1 keeps attestation human-approved through the MCP tool). This is the shell
57
+ entry point a session-end Stop hook calls. Verified end-to-end.
58
+
59
+ The two firing mechanisms are complementary: the orchestrator convention delivers
60
+ proposals to a present human for approval; the Stop hook guarantees the trigger fires
61
+ and is logged even when the orchestrator does not. The settings.json Stop-hook wiring is
62
+ a per-project harness step (not shipped in the gem). 17 Slice-3 unit tests green (incl.
63
+ trigger-source); Slices 1 (35) and 2 (25) unchanged.
64
+
65
+ ## [3.37.0] - 2026-07-05
66
+
67
+ ### Added — Synoptis L2 attestation: semantic criterion + snapshot (Slice 3a)
68
+
69
+ Matures the ACT-2 selection criterion and adds the optional content snapshot
70
+ (§Kinds), without embedding an LLM call inside the tool (selective-survival: the
71
+ in-Ruby LLM scoring is deferred until the frontmatter criterion is observed to be
72
+ insufficient).
73
+
74
+ - **Semantic layer (ACT-2)**: `l2_attestation_scan` now attaches a bounded content
75
+ `preview` to each proposal and surfaces a `rubric` (config `semantic_rubric`). The
76
+ orchestrator applies the rubric to the previews to decide which frontmatter-filtered
77
+ candidates to propose to the human — the LLM-semantic judgment lives in the
78
+ orchestrator, not in the tool. The criterion stays inspectable and revisable by
79
+ editing config (`judgment_types`, `semantic_rubric`, `preview_chars`).
80
+ - **Snapshot (§Kinds optional)**: `l2_attestation_commit(embed_snapshot: true)` embeds
81
+ the subject's current content in the entry, so audits needing the original wording
82
+ have it (LED-2a preserves it). The snapshot's SHA256 equals the entry digest, so it is
83
+ self-verifying. Bounded by `max_snapshot_bytes` (default 256KB); oversize content is
84
+ refused (`snapshot_too_large`) rather than truncated.
85
+ - `SubjectRef` gains `content_text` / `content_preview`.
86
+
87
+ Deferred (until observed need): in-Ruby `llm_client` scoring of candidates; session-end
88
+ auto-firing of the trigger (ACT-5 liveness is currently satisfied by explicit/orchestrator
89
+ scan). 12 Slice-3 unit tests + 4 tool integration tests green; Slices 1 (35) and 2 (25)
90
+ unchanged.
91
+
92
+ ## [3.36.0] - 2026-07-05
93
+
94
+ ### Added — Synoptis L2 attestation: supersession + revocation (Slice 2)
95
+
96
+ Records the rewrite/withdrawal lifecycle of a constitutive attestation by appending,
97
+ never editing (the bank-ledger correction model). Completes §Kinds' two attestation-
98
+ ledger entry kinds and satisfies LED-2b (lineage) and ACT-3 (approval binding).
99
+
100
+ - **Supersession**: a re-attestation of an already-attested subject is now a
101
+ supersession — `l2_attestation_commit` auto-derives the current head and commits its
102
+ entry_id as `target_ref` (ContentAttestationEntry gains `target_ref`). The first
103
+ attestation about a subject carries none.
104
+ - **Revocation-withdrawal**: new `RevocationWithdrawalEntry` kind (commits subject +
105
+ target_ref + moment, no digest/content) and a new `l2_attestation_revoke` tool. It
106
+ appends a withdrawal of the subject's current head; like any ledger entry it requires
107
+ human approval (ACT-1) and is itself append-only, so "claimed then withdrawn" survives.
108
+ - **ACT-3 binding**: the commit/revoke proposal returns the digest and the target it
109
+ would act on; the approving call echoes them as `expected_digest` /
110
+ `expected_target_ref`. If the live content changed (digest_mismatch) or the head moved
111
+ (target_moved) between proposal and approval, the append is refused, not silent.
112
+ - **Fold**: `AttestationChain#current_head` / `current_state` fold a subject's entries
113
+ (first → supersessions → withdrawals) into a status (none/attested/withdrawn); a new
114
+ read-only `l2_attestation_view` tool surfaces it. Fold ordering rule fixed per §11: a
115
+ revocation clears the head only when it targets the current head.
116
+
117
+ Synoptis's trust engine (duplicate hard-reject, TTL, revocation store) is untouched —
118
+ the constitutive ledger is a separate chain, so re-attest-as-supersession is native and
119
+ no reconciliation was needed. 25 Slice-2 unit tests + 14 tool integration tests green;
120
+ Slice 1 (35) unchanged.
121
+
122
+ ## [3.35.1] - 2026-07-05
123
+
124
+ ### Fixed — Synoptis L2 attestation: frontmatter parsing dropped all proposals
125
+
126
+ `l2_attestation_scan` surfaced zero proposals against real L2 contexts because
127
+ `SubjectRef.extract_frontmatter` used `YAML.safe_load` without permitting `Date`:
128
+ a `date:` field (present in every real context's frontmatter) raised
129
+ `Psych::DisallowedClass`, which was rescued to `nil`, so the `type` was never read
130
+ and no context matched the criterion. Caught by live end-to-end verification, not
131
+ by unit tests (whose fixtures lacked a `date:` field). Fix permits `[Date, Time]`
132
+ and adds a regex fallback for the single `type` scalar so the criterion is robust
133
+ against any frontmatter YAML that `safe_load` rejects. Unit fixtures now carry a
134
+ `date:` field to guard the regression.
135
+
7
136
  ## [3.35.0] - 2026-07-05
8
137
 
9
138
  ### Added — Synoptis: L2 constitutive attestation (Slice 1)
data/bin/kairos-chain CHANGED
@@ -343,6 +343,86 @@ when 'upgrade'
343
343
  end
344
344
  exit
345
345
 
346
+ when 'attestation'
347
+ # Constitutive L2 attestation CLI (Slice 3c). Currently one action: `scan`, the
348
+ # ACT-5 trigger point invokable from a session-end Stop hook so that firing +
349
+ # trigger-record logging is guaranteed even when the orchestrator does not run the
350
+ # MCP tool. It proposes only and logs a trigger record; it never attests (ACT-1
351
+ # keeps attestation human-approved through the MCP tool).
352
+ ARGV.shift # Remove 'attestation'
353
+
354
+ $LOAD_PATH.unshift File.expand_path('../lib', __dir__)
355
+ require 'kairos_mcp'
356
+
357
+ if (idx = ARGV.index('--data-dir'))
358
+ KairosMcp.data_dir = File.expand_path(ARGV[idx + 1])
359
+ ARGV.delete_at(idx + 1)
360
+ ARGV.delete_at(idx)
361
+ end
362
+
363
+ action = ARGV.shift
364
+ case action
365
+ when 'scan'
366
+ require 'json'
367
+ require 'yaml'
368
+ require 'fileutils'
369
+
370
+ session = (i = ARGV.index('--session')) ? ARGV[i + 1] : nil
371
+ source = (i = ARGV.index('--source')) ? ARGV[i + 1] : 'session_end_hook'
372
+
373
+ syn_lib = File.join(KairosMcp.skillsets_dir, 'synoptis', 'lib')
374
+ unless File.directory?(syn_lib)
375
+ puts JSON.generate(status: 'error', message: 'synoptis skillset not installed')
376
+ exit 0
377
+ end
378
+ $LOAD_PATH.unshift(syn_lib)
379
+ require 'synoptis/registry/file_registry'
380
+ require 'synoptis/constitutive/subject_ref'
381
+ require 'synoptis/constitutive/proposal_criterion'
382
+ require 'synoptis/constitutive/attestation_chain'
383
+
384
+ context_dir = KairosMcp.context_dir
385
+ session ||= if Dir.exist?(context_dir)
386
+ Dir.children(context_dir)
387
+ .select { |c| File.directory?(File.join(context_dir, c)) }
388
+ .max_by { |c| File.mtime(File.join(context_dir, c)) }
389
+ end
390
+ unless session
391
+ puts JSON.generate(status: 'error', message: 'no session found to scan')
392
+ exit 0
393
+ end
394
+
395
+ cfg_path = File.join(KairosMcp.skillsets_dir, 'synoptis', 'config', 'synoptis.yml')
396
+ const_cfg = (File.exist?(cfg_path) ? (YAML.safe_load(File.read(cfg_path)) || {}) : {})['constitutive'] || {}
397
+
398
+ data_dir = File.join(KairosMcp.data_dir, 'synoptis_data')
399
+ FileUtils.mkdir_p(data_dir)
400
+ registry = Synoptis::Registry::FileRegistry.new(data_dir: data_dir)
401
+ chain = Synoptis::Constitutive::AttestationChain.new(registry: registry)
402
+ criterion = Synoptis::Constitutive::ProposalCriterion.new(
403
+ context_dir: context_dir,
404
+ judgment_types: const_cfg['judgment_types'] ||
405
+ Synoptis::Constitutive::ProposalCriterion::DEFAULT_JUDGMENT_TYPES,
406
+ preview_chars: const_cfg['preview_chars'] ||
407
+ Synoptis::Constitutive::ProposalCriterion::DEFAULT_PREVIEW_CHARS
408
+ )
409
+
410
+ proposals = criterion.propose(session_id: session)
411
+ chain.append_trigger(surfaced_count: proposals.length, source: source)
412
+ puts JSON.generate(
413
+ status: 'proposed',
414
+ session_id: session,
415
+ trigger_source: source,
416
+ surfaced_count: proposals.length,
417
+ proposals: proposals
418
+ )
419
+ exit 0
420
+ else
421
+ warn "Unknown attestation action: #{action.inspect}"
422
+ warn 'Usage: kairos-chain attestation scan [--session SID] [--source SRC] [--data-dir DIR]'
423
+ exit 1
424
+ end
425
+
346
426
  when 'mode'
347
427
  ARGV.shift # Remove 'mode' from ARGV
348
428
 
@@ -1,4 +1,4 @@
1
1
  module KairosMcp
2
- VERSION = "3.35.0"
2
+ VERSION = "3.39.0"
3
3
  CHANGELOG_URL = "https://github.com/masaomi/KairosChain_2026/blob/main/CHANGELOG.md"
4
4
  end
@@ -65,6 +65,11 @@ different bugs.
65
65
 
66
66
  ### Design Phase
67
67
 
68
+ #### Pre-draft: Unknowns Pass
69
+ - For artifacts routed to full multi-LLM review, run the Unknowns Pass
70
+ (blindspot enumeration + human interview) **before** drafting v0.1 —
71
+ see `multi_llm_review_workflow` Step 0.25 (v3.6+).
72
+
68
73
  #### Solo Design (v0.1)
69
74
  - Single LLM (Opus-class) produces initial design
70
75
  - Include: architecture, component design, schema, error handling, phase boundaries
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: multi_llm_review_workflow
3
3
  description: "Multi-LLM review methodology and execution — workflow pattern, CLI tooling, consensus analysis, Persona Assembly. Applicable to design, implementation, documentation, or any artifact."
4
- version: "3.5"
4
+ version: "3.6"
5
5
  tags:
6
6
  - workflow
7
7
  - review
@@ -43,6 +43,154 @@ Codex (c)-class value-divergent REJECTs as blocking, which causes review loops t
43
43
  fail to converge. The cross-reference exists in `related:` frontmatter; this step
44
44
  makes it an explicit pre-condition rather than an implicit hint.
45
45
 
46
+ ## Step 0.25 — Unknowns Pass (pre-draft, qualifying reviews only)
47
+
48
+ > **Numbering vs timing**: Step 0 and Step 0.5 execute at review time,
49
+ > immediately before dispatch. Step 0.25 executes **pre-draft** — earlier in
50
+ > wall-clock time than both. The numeric order is document order, not
51
+ > execution order.
52
+
53
+ **Applies to** artifacts that this workflow's decision heuristic routes to
54
+ full multi-LLM review, in the design-phase and knowledge/documentation
55
+ review types (design review, knowledge/documentation-update review, document
56
+ review). Throughout this step, "qualifying review" means exactly this set.
57
+ Artifacts below that threshold (single-LLM review, self-review, skip) are
58
+ exempt — the pass inherits the existing tier heuristic rather than
59
+ introducing a second gate. Implementation-phase reviews are out of scope.
60
+
61
+ The problem this step solves: the workflow otherwise begins at "artifact
62
+ exists," so unknowns the author never considered enter drafts undetected and
63
+ are excavated by reviewers round by round, inflating round counts. The pass
64
+ moves unknown discovery from the expensive channel (review rounds) to the
65
+ cheap channel (pre-draft dialogue), and makes the residue explicit.
66
+ (Source: Thariq Shihipar, "A Field Guide to Fable: Finding Your Unknowns,"
67
+ 2026-07-03 — techniques ① blindspot pass and ③ interview. Design record:
68
+ `docs/drafts/multi_llm_review_unknowns_pass_v0.3.1_FROZEN.md`.)
69
+
70
+ ### Structure of the pass
71
+
72
+ Step 0.25 runs after the decision to produce a qualifying artifact and
73
+ before its first draft. Two moves, one triage:
74
+
75
+ 1. **Blindspot enumeration.** The orchestrator, holding full project
76
+ context, enumerates the questions most likely to change the design if
77
+ answered differently — *without answering them itself*. This is a bounded
78
+ search discipline, not a completeness guarantee: the pass is judged by
79
+ whether it reliably surfaces the highest-impact questions available to
80
+ the orchestrator's current context (nothing-missed is unachievable per
81
+ Prop 6).
82
+ 2. **Interview.** The orchestrator puts the enumerated questions to the
83
+ human, one question at a time, ordered by decision impact. The interview
84
+ ends when both parties judge that remaining questions will only be
85
+ answerable once a draft exists. Ending the interview does not discharge
86
+ the triage below: every enumerated unknown — including those the
87
+ interview never reached — still exits through it.
88
+
89
+ **Triage.** Every surfaced unknown exits the pass in exactly one of two
90
+ **terminal** states, possibly via one **transient** state:
91
+
92
+ - **Resolved** (terminal) — answered by the human; the answer feeds the
93
+ Design Direction Block (Step 0.5).
94
+ - **Declared** (terminal) — recorded in the artifact as an *Open Unknown*
95
+ and registered in the artifact's backlog section. A human's explicit
96
+ decision *not* to answer a question is itself a human judgment and routes
97
+ the unknown here — a legitimate attended outcome.
98
+ - **Draft-deferred** (transient, non-terminal) — marked for mandatory
99
+ re-triage after drafting. Re-triage collapses each draft-deferred unknown
100
+ into Resolved or Declared **before review dispatch** (a precondition of
101
+ dispatch under INV-U1). INV-U2 applies at re-triage exactly as at the
102
+ interview: collapsing to Resolved requires the human's answer, so
103
+ unattended re-triage can only collapse to Declared.
104
+
105
+ ### Invariants
106
+
107
+ **INV-U1 (front-load gate).** Round 1 of a qualifying review is not
108
+ dispatched for an artifact that has not passed through the Unknowns Pass.
109
+ At dispatch time, every unknown the pass surfaced is in a terminal state:
110
+ resolved by the human, or declared in the artifact. A draft-deferred unknown
111
+ still in its transient state blocks dispatch until re-triaged.
112
+
113
+ **INV-U2 (human gate).** The answerer in the interview is the human. The
114
+ orchestrator posing a question and answering it itself does not constitute
115
+ resolution. (Same conceptual line as ACT-1 in L2 attestation — analogical,
116
+ not a shared implementation: judgment-shaped decisions belong to the human.)
117
+
118
+ **INV-U3 (constitutive recording).** The products of the pass — enumerated
119
+ questions, human answers, declared Open Unknowns — are recorded to L2. A
120
+ discovered unknown is an asset, not a consumable. The record is written
121
+ unconditionally: a pass that surfaces no unknowns writes an explicit
122
+ zero-result record, which is what makes a skipped pass distinguishable from
123
+ a pass that surfaced nothing. The record is instance-local; outbound sharing
124
+ is governed by the instance's existing outbound-sharing discipline — this
125
+ invariant authorizes no new disclosure surface.
126
+
127
+ **INV-U4 (declared unknowns are non-blocking — bounded).** A reviewer
128
+ finding that merely restates a declared Open Unknown is classified (c)
129
+ advisory by default, subject to two bounds:
130
+
131
+ - *Provenance bound*: the demotion applies only to unknowns whose specific
132
+ declaration a human has seen — declared through an attended pass, or
133
+ ratified by a human at a subsequent attended session. Declarations made by
134
+ an orchestrator alone (including unattended declarations under a mandate's
135
+ category-level pre-classification) carry no demotion power until so
136
+ ratified.
137
+ - *Inverted default*: when the orchestrator is uncertain whether a finding
138
+ merely restates a declared unknown or shows that the declared deferral is
139
+ itself unsafe, the finding is treated as blocking. This deliberately
140
+ inverts the usual "unsure between (b) and (c) → (c)" rule, because here
141
+ the doubt concerns whether a gate is being laundered, and gates fail
142
+ closed.
143
+
144
+ Findings about the *integrity* of the design — internal contradiction,
145
+ unrealizable invariant, an Open Unknown whose declared deferral is itself
146
+ unsafe — remain (a)/(b) and block as before.
147
+
148
+ **INV-U5 (classification authority).** The judgment-shaped /
149
+ non-judgment-shaped classification of an unknown is a human prerogative.
150
+ Attended, the human exercises it through the interview itself (answering,
151
+ declining, or judging a question draft-answerable). Unattended, every
152
+ surfaced unknown is judgment-shaped by default; only a human-authored
153
+ mandate may pre-classify named categories as non-judgment-shaped for a given
154
+ run. An orchestrator's self-classification has no force. (Fail-closed:
155
+ unclassified ⇒ judgment-shaped ⇒ stop.)
156
+
157
+ ### Unattended execution (autonomous loops)
158
+
159
+ In an unattended context, the "resolved" exit is unavailable. INV-U2 is
160
+ preserved — the system does not self-answer — and INV-U5 governs
161
+ classification:
162
+
163
+ - **Judgment-shaped** (the unattended default for ALL unknowns): fail-closed
164
+ stop; the question is recorded and queued for the next attended session.
165
+ - **Non-judgment-shaped** (only via human-authored mandate
166
+ pre-classification): declared as Open Unknown; the run proceeds, but per
167
+ INV-U4's provenance bound the declaration carries no demotion power until
168
+ human-ratified — reviewer restatements remain blocking.
169
+
170
+ Consequence: an unattended run without a mandate cannot proceed past any
171
+ surfaced unknown. This is intended — it makes "unattended design review
172
+ without human pre-delegation" structurally inert rather than quietly
173
+ self-certifying, and liberal declaring yields no convergence advantage.
174
+
175
+ Stop semantics, as an invariant: repeated unattended encounters with the
176
+ same pending question produce no new side effects and no forward motion —
177
+ re-stopping is idempotent; forward motion requires a human answer. The
178
+ mandate's expressive power, the ratification protocol, and the
179
+ pending-question queue mechanism are owned by the Autonomous Growth Loop
180
+ guard track (see the frozen design's §11 backlog).
181
+
182
+ ### Acceptance observation (selective survival)
183
+
184
+ This step is a discipline under observation, not settled infrastructure.
185
+ From adoption onward, during synthesis the orchestrator tags each blocking
186
+ finding — and each finding demoted under INV-U4 — as kind `open-question`
187
+ (unresolved design decision) or kind `defect` (flaw in a made decision),
188
+ recorded in the round's L2 review record. Survival judgment (human's, on
189
+ recorded counts): round-1 `open-question` counts should trend toward zero
190
+ across the first 2–3 qualifying loops; no INV-U4-demoted finding re-tagged
191
+ `defect` within its loop + next revision cycle; interviews staying within
192
+ ~5 questions.
193
+
46
194
  ## Step 0.5 — Design Direction Block (design / docs reviews only)
47
195
 
48
196
  For **design-phase** and **knowledge/documentation-update** reviews, prepend a
@@ -93,6 +241,10 @@ mistaken for genuine defects.
93
241
  (e.g., "invariant declaration > mechanism enumeration, per project
94
242
  design-by-invariant principle")
95
243
 
244
+ **Declared Open Unknowns** (carried in from Step 0.25, if any):
245
+ - <unknown>: declared open; findings that merely restate it are (c) advisory
246
+ per INV-U4 (subject to its provenance bound and inverted default)
247
+
96
248
  **Where to register additions/objections**:
97
249
  - New mechanisms or scope expansions → §11 backlog of the artifact, not body
98
250
  - Style/readability concerns not entailed by project principles → (c)
@@ -250,13 +402,21 @@ The user always has the final say.
250
402
  ## Workflow Pattern
251
403
 
252
404
  ```
405
+ [0.25] Unknowns Pass (pre-draft; qualifying reviews only — see Step 0.25)
406
+ |
407
+ ├── blindspot enumeration + human interview
408
+ └── L2 context: pass record (unconditional, incl. zero-result)
409
+ |
253
410
  [1] Primary LLM creates artifact → outputs artifact + review prompt
254
411
  |
255
412
  ├── artifact: log/{name}_{llm}_{date}.md
413
+ ├── re-triage: draft-deferred unknowns → Resolved or Declared
256
414
  ├── review prompt: log/{name}_review_prompt.md
257
415
  └── L2 context: saved via context_save
258
416
  |
259
417
  [2] Orchestrator sends prompt → N reviewer LLMs execute in parallel
418
+ (for qualifying reviews, conditioned on INV-U1: pass record exists
419
+ and every surfaced unknown is terminal)
260
420
  |
261
421
  ├── Reviewer 1: independent review
262
422
  ├── Reviewer 2: independent review
@@ -269,6 +429,10 @@ The user always has the final say.
269
429
  └── L2 save: consensus + revised artifact
270
430
  |
271
431
  [4] Classify findings as (a)/(b)/(c) per `multi_llm_reviewer_evaluation`
432
+ Apply INV-U4: findings merely restating a declared Open Unknown → (c)
433
+ advisory (provenance bound + inverted default apply — see Step 0.25)
434
+ Tag each blocking finding and each INV-U4-demoted finding as kind
435
+ `open-question` or `defect` in the round's L2 record
272
436
  If no (a)/(b) blocking findings → proceed to next phase
273
437
  If any (a)/(b) finding → repeat from [2] with revised artifact
274
438
  (c) findings are recorded as advisory; non-blocking
@@ -375,9 +539,15 @@ When the orchestrator integrates N reviews:
375
539
  ## L2 Save Points
376
540
 
377
541
  Save to L2 context at these moments:
542
+ - **After the Unknowns Pass** (Step 0.25; unconditional, including
543
+ zero-result passes, per INV-U3): enumerated questions, human answers,
544
+ declared Open Unknowns
378
545
  - After design/implementation complete (before review)
379
546
  - After synthesis of reviews (revised version)
380
547
  - After final convergence (implementation-ready / merge-ready)
548
+ - **Per-round finding tagging** (Step 0.25 acceptance observation): each
549
+ blocking finding and each INV-U4-demoted finding tagged `open-question`
550
+ or `defect` in the round's review record
381
551
  - **After each review round**: capture per-reviewer observations — verdict,
382
552
  (a)/(b)/(c) classification breakdown, briefing-reaction shift (did the
383
553
  reviewer change verdict after Step 0.5 design direction?), anomalies
@@ -818,6 +988,15 @@ Compression ratio: parallel agent raw → Assembly ≈ 2:1
818
988
  - Self-referential review: v3.2 (4-reviewer update, 2026-04-19) reviewed with new 4-reviewer default (Opus 4.6 + 4.7 + Codex + Composer-2). 4/4 APPROVE WITH CHANGES R1. Findings integrated → v3.3
819
989
  - Roster update (v3.5, 2026-06-10): Fable 5 replaces Opus 4.7 as orchestrator/team slot; Opus 4.8 added as second subprocess CLI reviewer alongside Opus 4.6. 4.6 retained for its documented complementary bias (ambiguity-preserving, self-reference-friendly); 4.7 retired as its register is covered by 4.8 and Fable 5. 4.8/Fable 5 bias profiles uncalibrated — record (a)/(b)/(c) breakdowns per round until profiles accumulate in `multi_llm_reviewer_evaluation`
820
990
  - Self-referential review of v3.5 (2 rounds, 2026-06-10/11, first run of the 6-reviewer roster): R1 REVISE (1 APPROVE / 4 REJECT — stale pre-v3.5 passages) → fixes → R2 3/6 APPROVE (4.6, 4.8, codex 5.4) with Cursor contributing a code-grounded correction (subprocess strategy keeps the full roster). 4.6/4.8 verdicts split along the predicted lenient/strict axis in R1 and converged to APPROVE in R2
991
+ - Step 0.25 Unknowns Pass added (v3.6, 2026-07-05): designed via its own
992
+ 3-round self-referential review (R1 REVISE 2/6 → R2 REVISE 2/6 → R3 4/6
993
+ APPROVE; v0.3.1 FROZEN). The loop demonstrated the step's own thesis:
994
+ R1's two biggest blockers (unattended classification authority, INV-U4
995
+ demotion bounds) were exactly judgment-shaped unknowns a pre-draft
996
+ interview would have caught. Also observed: fix-verification requires
997
+ epistemic diversity — the personas that authored R1 findings all approved
998
+ their own fixes in R2, while subprocess reviewers caught the seams.
999
+ Design record: `docs/drafts/multi_llm_review_unknowns_pass_v0.3.1_FROZEN.md`
821
1000
 
822
1001
  **Key insight**: Design reviews and implementation reviews find
823
- **categorically different bugs**. Both phases are necessary.
1002
+ **categorically different bugs**. Both phases are necessary.
@@ -56,6 +56,16 @@ constitutive:
56
56
  - handoff
57
57
  - decision
58
58
  - debrief
59
+ # ACT-2 semantic layer (Slice 3): the orchestrator applies this rubric to each
60
+ # proposal's preview before proposing to the human. Revisable — this is the criterion.
61
+ semantic_rubric: >
62
+ Propose a context for attestation when it records a judgment, decision, or outcome
63
+ whose "as recorded at the time" matters for a later audit — e.g. a handoff of state,
64
+ a design decision with rationale, or a debrief of what happened. Do NOT propose pure
65
+ scratch notes, transient status, or content with no decisional weight. When in doubt,
66
+ prefer proposing (the human gate, ACT-1, is the safeguard).
67
+ preview_chars: 300 # bounded excerpt surfaced per proposal
68
+ max_snapshot_bytes: 262144 # 256KB cap on an optional embedded snapshot
59
69
 
60
70
  challenge:
61
71
  response_timeout: 3600 # 1 hour
@@ -23,21 +23,71 @@ module Synoptis
23
23
  @registry = registry
24
24
  end
25
25
 
26
- # ACT-1: append an approved content-attestation entry to the ledger.
26
+ # ACT-1: append an approved content-attestation entry to the ledger. A first
27
+ # attestation carries no target_ref; a supersession carries the entry_id of the
28
+ # entry it supersedes (LED-2b).
27
29
  def append_content_attestation(entry)
28
30
  @registry.append(LEDGER, entry.to_h)
29
31
  end
30
32
 
33
+ # ACT-1: append an approved revocation-withdrawal entry to the ledger. It withdraws
34
+ # the referenced target entry (§Kinds); it is itself append-only (LED-2), so the
35
+ # withdrawal is recorded, not an erasure.
36
+ def append_revocation_withdrawal(entry)
37
+ @registry.append(LEDGER, entry.to_h)
38
+ end
39
+
31
40
  def entries
32
41
  @registry.read(LEDGER)
33
42
  end
34
43
 
44
+ def entries_for(subject_id)
45
+ entries.select { |e| e[:subject_id] == subject_id }
46
+ end
47
+
48
+ # The current "head" of a subject: the fold (LED-2b, §11) over its entries'
49
+ # target references. Walking in append order, a content-attestation (first or
50
+ # supersession) becomes the head; a revocation-withdrawal that targets the current
51
+ # head clears it. Returns the head entry hash, or nil if the subject has never been
52
+ # attested or its latest attestation has been withdrawn.
53
+ #
54
+ # Ordering rule (fixed here per §11): a revocation-withdrawal only clears the head
55
+ # when it targets the current head; a withdrawal of an already-superseded (non-head)
56
+ # entry is recorded but does not change the head.
57
+ def current_head(subject_id)
58
+ head = nil
59
+ entries_for(subject_id).each do |e|
60
+ case e[:kind]
61
+ when 'content_attestation'
62
+ head = e
63
+ when 'revocation_withdrawal'
64
+ head = nil if head && e[:target_ref] == head[:entry_id]
65
+ end
66
+ end
67
+ head
68
+ end
69
+
70
+ # A human-readable fold of a subject's trajectory: current status + full history.
71
+ def current_state(subject_id)
72
+ history = entries_for(subject_id)
73
+ head = current_head(subject_id)
74
+ status =
75
+ if history.empty? then 'none'
76
+ elsif head.nil? then 'withdrawn'
77
+ else 'attested'
78
+ end
79
+ { subject_id: subject_id, status: status, head: head, history: history }
80
+ end
81
+
35
82
  # ACT-5: telemetry that the criterion fired and surfaced N proposals. Subject-free
36
83
  # and content-free, so it neither indexes L2 (LED-4) nor records a per-context
37
- # verdict (ACT-4).
38
- def append_trigger(surfaced_count:, moment: nil)
84
+ # verdict (ACT-4). `source` names which trigger point fired (manual /
85
+ # orchestrator_session_end / session_end_hook), so the "at least one defined trigger
86
+ # point" of ACT-5 is distinguishable in the operational log.
87
+ def append_trigger(surfaced_count:, source: 'manual', moment: nil)
39
88
  @registry.append(OPLOG, {
40
89
  record: 'trigger',
90
+ source: source,
41
91
  surfaced_count: surfaced_count,
42
92
  moment: moment || Time.now.utc.iso8601
43
93
  })
@@ -15,20 +15,31 @@ module Synoptis
15
15
  # (ACT-1), not crypto-gated, and an attestation entry does not expire (design §11).
16
16
  # This is posture parity with L0/L1 (LED-6), not a regression against Synoptis's
17
17
  # signed ProofEnvelope — it is a different kind for a different purpose.
18
+ #
19
+ # A `target_ref` (Slice 2, LED-2b) marks this entry a SUPERSESSION: it commits the
20
+ # entry_id of the prior entry it supersedes. The first content-attestation about a
21
+ # subject carries none; a re-attestation of an already-attested subject is a
22
+ # supersession (§Kinds).
18
23
  class ContentAttestationEntry
19
24
  KIND = 'content_attestation'
20
25
  DEFAULT_DIGEST_ALG = 'sha256'
21
26
 
22
- attr_reader :entry_id, :subject_id, :digest, :digest_alg, :moment, :snapshot
27
+ attr_reader :entry_id, :subject_id, :digest, :digest_alg, :moment, :snapshot, :target_ref
23
28
 
24
29
  def initialize(subject_id:, digest:, moment:, digest_alg: DEFAULT_DIGEST_ALG,
25
- snapshot: nil, entry_id: nil)
30
+ snapshot: nil, entry_id: nil, target_ref: nil)
26
31
  @entry_id = entry_id || SecureRandom.uuid
27
32
  @subject_id = subject_id
28
33
  @digest = digest
29
34
  @digest_alg = digest_alg
30
35
  @moment = moment
31
36
  @snapshot = snapshot
37
+ @target_ref = target_ref
38
+ end
39
+
40
+ # A supersession points at the entry it supersedes (LED-2b).
41
+ def supersession?
42
+ !@target_ref.nil?
32
43
  end
33
44
 
34
45
  def to_h
@@ -39,7 +50,8 @@ module Synoptis
39
50
  digest: @digest,
40
51
  digest_alg: @digest_alg,
41
52
  moment: @moment,
42
- snapshot: @snapshot
53
+ snapshot: @snapshot,
54
+ target_ref: @target_ref
43
55
  }
44
56
  end
45
57
 
@@ -62,7 +74,8 @@ module Synoptis
62
74
  moment: hash[:moment],
63
75
  digest_alg: hash[:digest_alg] || DEFAULT_DIGEST_ALG,
64
76
  snapshot: hash[:snapshot],
65
- entry_id: hash[:entry_id]
77
+ entry_id: hash[:entry_id],
78
+ target_ref: hash[:target_ref]
66
79
  )
67
80
  end
68
81
  end