@smartmemory/compose 0.3.6-beta → 0.3.8
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.
- package/.claude/skills/compose/SKILL.md +42 -88
- package/.compose-deps.json +1 -13
- package/README.md +72 -5
- package/bin/compose.js +754 -347
- package/bin/git-hooks/pre-push.template +29 -0
- package/bin/judgment-import.js +7 -0
- package/bin/judgment-migrate.js +387 -0
- package/contracts/comp-obs-contract.schema.json +9 -3
- package/contracts/feature-json.schema.json +5 -0
- package/contracts/fluid-record.schema.json +209 -0
- package/contracts/judgment-record.schema.json +425 -4
- package/contracts/lifecycle-backfill.schema.json +322 -0
- package/dist/assets/App-Z4MU-H_F.js +916 -0
- package/dist/assets/_baseUniq-ClWoCPFl.js +1 -0
- package/dist/assets/arc-DY26UIVo.js +1 -0
- package/dist/assets/architectureDiagram-Q4EWVU46-6Ggq4DqJ.js +36 -0
- package/dist/assets/blockDiagram-DXYQGD6D-CH3Ked0l.js +132 -0
- package/dist/assets/{browser-BSM23If2.js → browser-BWkrenen.js} +6 -6
- package/dist/assets/{c4Diagram-LMCZKHZV-DZf45Fbz.js → c4Diagram-AHTNJAMY-Bk8dYilu.js} +1 -1
- package/dist/assets/channel-SnZzzh7k.js +1 -0
- package/dist/assets/{chunk-JWPE2WC7-_7ujgd_Q.js → chunk-4BX2VUAB-BMR0XaAQ.js} +1 -1
- package/dist/assets/chunk-4TB4RGXK-JytR14a9.js +206 -0
- package/dist/assets/{chunk-XXDRQBXY-DfdVhbmA.js → chunk-55IACEB6-B4Q97BCP.js} +1 -1
- package/dist/assets/{chunk-VR4S4FIN-Dt9NZ67m.js → chunk-EDXVE4YY-R_qarkSf.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-BY4_PV5H.js → chunk-FMBD7UC4-C9s7KR9m.js} +1 -1
- package/dist/assets/chunk-OYMX7WX6-BySQzVxc.js +231 -0
- package/dist/assets/{chunk-2Q5K7J3B-Dn1spZYu.js → chunk-QZHKN3VN-DdpSYZsW.js} +1 -1
- package/dist/assets/{chunk-32BRIVSS-pURGrJDk.js → chunk-YZCP3GAM-iE_tzriw.js} +1 -1
- package/dist/assets/classDiagram-6PBFFD2Q-CBu92dSH.js +1 -0
- package/dist/assets/classDiagram-v2-HSJHXN6E-CBu92dSH.js +1 -0
- package/dist/assets/clone-DgklGjHm.js +1 -0
- package/dist/assets/{cose-bilkent-JH36ORCC-BieYif4o.js → cose-bilkent-S5V4N54A-BdlU6ZX_.js} +1 -1
- package/dist/assets/dagre-KV5264BT-Cp3F5KTn.js +4 -0
- package/dist/assets/diagram-5BDNPKRD-DiR6_2q_.js +10 -0
- package/dist/assets/diagram-G4DWMVQ6-w0i-p5HX.js +24 -0
- package/dist/assets/diagram-MMDJMWI5-tIHhwUv3.js +43 -0
- package/dist/assets/diagram-TYMM5635-BAeY3B19.js +24 -0
- package/dist/assets/erDiagram-SMLLAGMA-Ckx_Knko.js +85 -0
- package/dist/assets/flowDiagram-DWJPFMVM-DeoNka6J.js +162 -0
- package/dist/assets/ganttDiagram-T4ZO3ILL-BmGnFbEg.js +292 -0
- package/dist/assets/gitGraphDiagram-UUTBAWPF-Dk48IHsx.js +106 -0
- package/dist/assets/graph-BNzKGvoy.js +1 -0
- package/dist/assets/graph-CI_1htl0.js +331 -0
- package/dist/assets/index-BEfrNBp8.js +123 -0
- package/dist/assets/index-yyrA5OZd.css +1 -0
- package/dist/assets/infoDiagram-42DDH7IO-BRf827i0.js +2 -0
- package/dist/assets/{ishikawaDiagram-FXEZZL3T-CzEB9fQS.js → ishikawaDiagram-UXIWVN3A-0kCZaeCM.js} +5 -5
- package/dist/assets/{journeyDiagram-5HDEW3XC-Bz8TCdz2.js → journeyDiagram-VCZTEJTY-rvU7ayRt.js} +1 -1
- package/dist/assets/{kanban-definition-HUTT4EX6-tozrMoV_.js → kanban-definition-6JOO6SKY-DpQwX1C5.js} +7 -7
- package/dist/assets/katex-DkKDou_j.js +257 -0
- package/dist/assets/layout-BI8cXFPI.js +1 -0
- package/dist/assets/{linear-Ck7gpa5N.js → linear-a0glcDiw.js} +1 -1
- package/dist/assets/min-vPHfnXcC.js +1 -0
- package/dist/assets/{mindmap-definition-LN4V7U3C-DTcHO0DJ.js → mindmap-definition-QFDTVHPH-D14eF-7C.js} +7 -7
- package/dist/assets/mobile-B7m9EO9D.js +17 -0
- package/dist/assets/pieDiagram-DEJITSTG-Cno-gETh.js +30 -0
- package/dist/assets/quadrantDiagram-34T5L4WZ-BUQM1Hfm.js +7 -0
- package/dist/assets/{requirementDiagram-TGXJPOKE-bnI2zJeT.js → requirementDiagram-MS252O5E-pOXlN2-q.js} +3 -3
- package/dist/assets/sankeyDiagram-XADWPNL6-Crynd3_b.js +10 -0
- package/dist/assets/sequenceDiagram-FGHM5R23-D9fZdCM8.js +157 -0
- package/dist/assets/stateDiagram-FHFEXIEX-CW9qVec8.js +1 -0
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-DkVLzHbY.js +1 -0
- package/dist/assets/{timeline-definition-FHXFAJF6-D267GQFF.js → timeline-definition-GMOUNBTQ-BcHzhm_8.js} +3 -3
- package/dist/assets/vennDiagram-DHZGUBPP-BfytJcWk.js +34 -0
- package/dist/assets/wardley-RL74JXVD-DLj-IjyB.js +162 -0
- package/dist/assets/wardleyDiagram-NUSXRM2D-Ds0Ue68c.js +20 -0
- package/dist/assets/xychartDiagram-5P7HB3ND-vjWDXFL6.js +7 -0
- package/dist/index.html +3 -3
- package/lib/agent-string.js +7 -5
- package/lib/append-integrity.js +81 -0
- package/lib/backfill-evidence.js +109 -0
- package/lib/bug-escalation.js +39 -4
- package/lib/build-stream-schema.js +3 -1
- package/lib/build-stream-writer.js +25 -0
- package/lib/build.js +1624 -195
- package/lib/canon-guard.js +245 -0
- package/lib/canon-override.js +196 -0
- package/lib/canon-registry.js +291 -0
- package/lib/cli-commands.js +144 -0
- package/lib/codex-preflight.js +50 -15
- package/lib/colleague/context.js +215 -0
- package/lib/colleague/writeback.js +95 -0
- package/lib/completion-gate.js +1421 -0
- package/lib/completion-writer.js +47 -47
- package/lib/consumer-fanout.js +105 -11
- package/lib/coverage-gate.js +200 -0
- package/lib/dir-lock.js +170 -0
- package/lib/dispatch-ledger.js +301 -0
- package/lib/dispatch-metrics.js +236 -0
- package/lib/experiment-judge.js +6 -1
- package/lib/feature-json.js +1 -1
- package/lib/feature-reconciler.js +8 -0
- package/lib/feature-validator.js +64 -1
- package/lib/feature-writer.js +66 -2
- package/lib/fluid/factory.js +167 -0
- package/lib/fluid/ideabox-dates.js +73 -0
- package/lib/fluid/ideabox-migrate.js +154 -0
- package/lib/fluid/ideabox-ops.js +585 -0
- package/lib/fluid/ideabox-view.js +146 -0
- package/lib/fluid/import-ideabox.js +186 -0
- package/lib/fluid/local-provider.js +606 -0
- package/lib/fluid/provider.js +684 -0
- package/lib/fluid/record-shape.js +214 -0
- package/lib/fluid/record-store.js +328 -0
- package/lib/fluid/render-ideabox.js +261 -0
- package/lib/fluid/schema.js +40 -0
- package/lib/fluid/smartmemory-provider.js +1695 -0
- package/lib/gsd.js +63 -14
- package/lib/guard-cli.js +175 -0
- package/lib/guard-custody.js +141 -0
- package/lib/guard-descriptors.js +530 -0
- package/lib/guard-enrol.js +254 -0
- package/lib/health-score.js +1 -1
- package/lib/hooks-status.js +32 -3
- package/lib/ideabox-cli.js +315 -0
- package/lib/ideabox.js +121 -21
- package/lib/judgment/store/index.js +166 -0
- package/lib/judgment/store/records.js +184 -25
- package/lib/judgment/trace.js +380 -0
- package/lib/judgment-attest.js +259 -0
- package/lib/judgment-decision-write.js +277 -0
- package/lib/judgment-decisions.js +466 -0
- package/lib/judgment-gen.js +375 -22
- package/lib/judgment-verify.js +153 -0
- package/lib/judgment-writer.js +2842 -262
- package/lib/lane-gate.js +2 -0
- package/lib/lifecycle-modes.js +4 -4
- package/lib/lineage.js +400 -0
- package/lib/local-claude-connector.js +250 -54
- package/lib/maya-client.js +302 -0
- package/lib/maya-config.js +53 -0
- package/lib/maya-identity.js +283 -0
- package/lib/mcp-enforcement.js +21 -35
- package/lib/migrate-anon.js +5 -0
- package/lib/migrate-roadmap.js +15 -0
- package/lib/new.js +13 -1
- package/lib/pipeline-compat.js +104 -0
- package/lib/policy-catalog.js +295 -0
- package/lib/policy-check.js +0 -0
- package/lib/process-termination.js +98 -0
- package/lib/resolve-workspace.js +5 -1
- package/lib/result-normalizer.js +428 -153
- package/lib/review-normalize.js +4 -0
- package/lib/roadmap-errors.js +65 -0
- package/lib/roadmap-preservers.js +24 -4
- package/lib/roadmap-residue.js +299 -0
- package/lib/smartmemory-client.js +614 -78
- package/lib/smartmemory-config.js +54 -0
- package/lib/smartmemory-ingest.js +19 -2
- package/lib/step-prompt.js +7 -6
- package/lib/stratum-engine.js +53 -4
- package/lib/stratum-mcp-client.js +391 -31
- package/lib/test-bootstrap.js +31 -0
- package/lib/tool-inventory.js +122 -0
- package/lib/version-check.js +91 -19
- package/lib/vision-writer.js +88 -1
- package/package.json +7 -6
- package/pipelines/bug-fix.stratum.yaml +205 -211
- package/pipelines/build-quick.profiles.json +12 -0
- package/pipelines/build-quick.stratum.yaml +263 -350
- package/pipelines/content.stratum.yaml +81 -77
- package/pipelines/coverage-sweep.stratum.yaml +49 -30
- package/pipelines/plan.stratum.yaml +76 -86
- package/pipelines/refactor.stratum.yaml +125 -125
- package/pipelines/research.stratum.yaml +56 -58
- package/pipelines/review-fix.profiles.json +6 -0
- package/pipelines/review-fix.stratum.yaml +110 -83
- package/presets/team-feature.profiles.json +6 -0
- package/presets/team-feature.stratum.yaml +93 -66
- package/presets/team-research.profiles.json +6 -0
- package/presets/team-research.stratum.yaml +89 -80
- package/presets/team-review.profiles.json +8 -0
- package/presets/team-review.stratum.yaml +98 -80
- package/scripts/cost-census.mjs +70 -0
- package/scripts/guard-sign/compose-guard-sign.sh +62 -0
- package/server/agent-health.js +22 -0
- package/server/agent-hooks.js +14 -1
- package/server/agent-server.js +5 -248
- package/server/agent-spawn.js +3 -4
- package/server/agent-workspace.js +294 -0
- package/server/build-routes.js +6 -5
- package/server/build-stream-bridge.js +53 -0
- package/server/cc-session-watcher.js +4 -1
- package/server/coalescing-buffer.js +7 -1
- package/server/completion-projection.js +228 -0
- package/server/compose-mcp-tools.js +124 -24
- package/server/compose-mcp.js +91 -790
- package/server/decision-event-emit.js +41 -2
- package/server/decision-event-id.js +17 -0
- package/server/decision-events-snapshot.js +3 -0
- package/server/design-routes.js +14 -8
- package/server/feature-scan.js +76 -2
- package/server/file-watcher.js +170 -21
- package/server/ideabox-routes.js +166 -224
- package/server/index.js +70 -100
- package/server/lifecycle-guard.js +240 -10
- package/server/lifecycle-phase-history.js +276 -0
- package/server/maya-routes.js +507 -0
- package/server/mcp-tool-defs.js +940 -0
- package/server/mcp-tool-policy.js +35 -3
- package/server/model-tiers.js +22 -5
- package/server/pipeline-routes.js +21 -11
- package/server/project-root.js +58 -19
- package/server/remote-utils.js +3 -1
- package/server/schema-validator.js +7 -1
- package/server/session-manager.js +5 -6
- package/server/session-routes.js +3 -1
- package/server/stratum-client.js +57 -10
- package/server/stratum-sync.js +6 -3
- package/server/summarizer.js +3 -4
- package/server/supervisor.js +0 -1
- package/server/vision-routes.js +208 -98
- package/server/vision-server.js +86 -23
- package/server/vision-store.js +60 -6
- package/server/vision-utils.js +3 -4
- package/server/workspace-activity.js +18 -0
- package/server/workspace-middleware.js +2 -2
- package/server/workspace-runtime.js +243 -0
- package/server/worktree-gc.js +1 -0
- package/dist/assets/App-BG3ngu8H.js +0 -896
- package/dist/assets/abnfDiagram-VRR7QNED-CjB_sD3D.js +0 -1
- package/dist/assets/arc-_v4hR_uD.js +0 -1
- package/dist/assets/architectureDiagram-ZJ3FMSHR-DreJmzXQ.js +0 -36
- package/dist/assets/blockDiagram-677ZJIJ3-BG9-c0O1.js +0 -132
- package/dist/assets/channel-B3U5wFAT.js +0 -1
- package/dist/assets/chunk-EX3LRPZG-DdELs1qP.js +0 -231
- package/dist/assets/chunk-MOJQB5TN-D-ky35G-.js +0 -88
- package/dist/assets/chunk-RYQCIY6F-Dag_kVlO.js +0 -1
- package/dist/assets/chunk-V7JOEXUC-BtewURat.js +0 -206
- package/dist/assets/classDiagram-OUVF2IWQ-B6fCN-ht.js +0 -1
- package/dist/assets/classDiagram-v2-EOCWNBFH-B6fCN-ht.js +0 -1
- package/dist/assets/cynefin-VYW2F7L2-CT2BA6KE.js +0 -178
- package/dist/assets/cynefinDiagram-TSTJHNR4-Bh6exbyg.js +0 -62
- package/dist/assets/dagre-VKFMJZFB-aXMLSmQL.js +0 -4
- package/dist/assets/diagram-FQU43EPY-Dr7JAOuQ.js +0 -3
- package/dist/assets/diagram-G47NLZAW-DUvA3FQK.js +0 -24
- package/dist/assets/diagram-NH7WQ7WH-BQUARqcu.js +0 -24
- package/dist/assets/diagram-OA4YK3LP-dDUc1zHi.js +0 -30
- package/dist/assets/diagram-WEI45ONY-B2h5Qlb1.js +0 -41
- package/dist/assets/ebnfDiagram-CCIWWBDH-DThRGupB.js +0 -1
- package/dist/assets/erDiagram-Q63AITRT-BUCsprO2.js +0 -85
- package/dist/assets/flowDiagram-23GEKE2U-DXtNNi6r.js +0 -156
- package/dist/assets/ganttDiagram-NO4QXBWP-D4zbBHh_.js +0 -292
- package/dist/assets/gitGraphDiagram-IHSO6WYX-DpoQws0W.js +0 -106
- package/dist/assets/graph-BXPQrYYB.js +0 -331
- package/dist/assets/graph-C9eacEi8.js +0 -1
- package/dist/assets/index-3ZH5eMcZ.js +0 -119
- package/dist/assets/index-LIwREYgH.css +0 -1
- package/dist/assets/infoDiagram-FWYZ7A6U-Bbas2GAo.js +0 -2
- package/dist/assets/katex-C5jXJg4s.js +0 -257
- package/dist/assets/layout-DEXfKzaS.js +0 -1
- package/dist/assets/map-Czzmt4hB.js +0 -1
- package/dist/assets/mobile-CaoXUwAr.js +0 -17
- package/dist/assets/pegDiagram-2B236MQR-CHiINrNy.js +0 -1
- package/dist/assets/pieDiagram-ENE6RG2P-CfS4YFlR.js +0 -39
- package/dist/assets/quadrantDiagram-ABIIQ3AL-CadesS9w.js +0 -7
- package/dist/assets/railroadDiagram-RFXS5EU6-CgWEspBN.js +0 -1
- package/dist/assets/sankeyDiagram-HTMAVEWB-YWKFgOGw.js +0 -40
- package/dist/assets/sequenceDiagram-DBY2YBRQ-BvkNOyF9.js +0 -162
- package/dist/assets/sizeCapture-X5ZJPWSS-DlFPA2yO.js +0 -1
- package/dist/assets/stateDiagram-2N3HPSRC-h8NIx0kQ.js +0 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-DjPgZtJ9.js +0 -1
- package/dist/assets/swimlanes-5IMT3BWC-CT5n22kG.js +0 -2
- package/dist/assets/swimlanesDiagram-G3AALYLV-Dn318Bhq.js +0 -8
- package/dist/assets/vennDiagram-L72KCM5P-Dj-wWLYG.js +0 -34
- package/dist/assets/wardleyDiagram-EHGQE667-BxCeYxkG.js +0 -78
- package/dist/assets/xychartDiagram-FW5EYKEG-DMFqWn7z.js +0 -7
- package/lib/staleness.js +0 -87
- package/server/ideabox-cache.js +0 -77
|
@@ -14,12 +14,25 @@
|
|
|
14
14
|
# (no flag change needed below). XREF_TARGET_MISSING stays error and blocks.
|
|
15
15
|
|
|
16
16
|
set -u
|
|
17
|
+
HOOK_VERSION="1"
|
|
17
18
|
COMPOSE_NODE="__COMPOSE_NODE__"
|
|
18
19
|
COMPOSE_BIN="__COMPOSE_BIN__"
|
|
19
20
|
COMPOSE_WORKSPACE_ID="__COMPOSE_WORKSPACE_ID__"
|
|
20
21
|
LOG="${COMPOSE_HOOK_LOG:-.compose/data/pre-push.log}"
|
|
21
22
|
mkdir -p "$(dirname "$LOG")" 2>/dev/null || true
|
|
22
23
|
|
|
24
|
+
# EAGAIN guard: git can hand hooks nonblocking stdout/stderr fds; a large echo
|
|
25
|
+
# (the validate output below) then dies mid-write with "echo: write error:
|
|
26
|
+
# Resource temporarily unavailable" and makes a successful push look failed.
|
|
27
|
+
# O_NONBLOCK lives on the open file description (shared across processes), so a
|
|
28
|
+
# child clearing it fixes the shell's own fds too.
|
|
29
|
+
command -v perl >/dev/null 2>&1 && perl -MFcntl -e '
|
|
30
|
+
for my $fd (1, 2) {
|
|
31
|
+
open(my $fh, ">&=", $fd) or next;
|
|
32
|
+
my $flags = fcntl($fh, F_GETFL, 0) // next;
|
|
33
|
+
fcntl($fh, F_SETFL, $flags & ~O_NONBLOCK);
|
|
34
|
+
}' 2>/dev/null || true
|
|
35
|
+
|
|
23
36
|
# ── Docs-only detection ──────────────────────────────────────────────────────
|
|
24
37
|
# Parse the ref updates git feeds on stdin (this MUST run before anything else
|
|
25
38
|
# touches stdin). If every pushed commit touches only docs/** or *.md, the test
|
|
@@ -61,6 +74,22 @@ if [ "$VALIDATE_EXIT" -ne 0 ]; then
|
|
|
61
74
|
echo "pre-push: compose validate reported drift (ADVISORY — not blocking). Review with \`compose validate\`." >&2
|
|
62
75
|
fi
|
|
63
76
|
|
|
77
|
+
# ── Judgment drift-detection gate (COMP-CANON-GUARD S5) ─────────────────────
|
|
78
|
+
# This runs for every push, including docs-only pushes: judgment canon is under
|
|
79
|
+
# docs/judgment/**, so placing it inside the test-gate skip would miss the most
|
|
80
|
+
# relevant push type. Accepted residual: git push --no-verify bypasses this hook.
|
|
81
|
+
echo "pre-push: checking judgment canon for drift…" >&2
|
|
82
|
+
"$COMPOSE_NODE" "$COMPOSE_BIN" guard verify >> "$LOG" 2>&1
|
|
83
|
+
GUARD_EXIT=$?
|
|
84
|
+
if [ "$GUARD_EXIT" -ne 0 ]; then
|
|
85
|
+
tail -n 40 "$LOG" >&2
|
|
86
|
+
echo "" >&2
|
|
87
|
+
echo "pre-push: judgment drift detected — push aborted (full log: $LOG)." >&2
|
|
88
|
+
echo "Run \`compose guard verify\` for details." >&2
|
|
89
|
+
exit "$GUARD_EXIT"
|
|
90
|
+
fi
|
|
91
|
+
echo "pre-push: judgment canon drift check green." >&2
|
|
92
|
+
|
|
64
93
|
# ── Test gate (COMP-RESUME follow-up) ───────────────────────────────────────
|
|
65
94
|
# Block the push if the suite is red. This is the gate whose absence let a broken
|
|
66
95
|
# integration test (and a stale-schema consumer bug) reach main — nothing ran the
|
package/bin/judgment-import.js
CHANGED
|
@@ -29,6 +29,10 @@ import {
|
|
|
29
29
|
judgmentJointAdd,
|
|
30
30
|
judgmentLedgerAppend,
|
|
31
31
|
} from '../lib/judgment-writer.js';
|
|
32
|
+
import {
|
|
33
|
+
computeRecordHashes,
|
|
34
|
+
writeManifest,
|
|
35
|
+
} from '../lib/judgment-attest.js';
|
|
32
36
|
import { getJudgmentValidator } from '../lib/judgment/schema.js';
|
|
33
37
|
|
|
34
38
|
const METHODS = ['EXT', 'INT', 'CONSTRUCT', 'ASSERT', 'STRADDLE'];
|
|
@@ -538,6 +542,9 @@ export async function runJudgmentImport(cwd, { dryRun = false } = {}) {
|
|
|
538
542
|
cpSync(join(staging, 'docs', 'judgment', 'records'), tmpDst, { recursive: true });
|
|
539
543
|
rename(tmpDst, dst);
|
|
540
544
|
regenerateProjections(cwd);
|
|
545
|
+
// Trust-on-first-use baseline for the promoted TARGET records. Never use
|
|
546
|
+
// the staging manifest here: its workspace paths are disposable.
|
|
547
|
+
writeManifest(cwd, computeRecordHashes(cwd));
|
|
541
548
|
}
|
|
542
549
|
|
|
543
550
|
return {
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* bin/judgment-migrate.js — ledger to SmartMemory decision migration
|
|
4
|
+
* (GOV-COMPOSE-SEAM-1 step 1 `canon-on-decisions`).
|
|
5
|
+
*
|
|
6
|
+
* Phase P1 ships `--dry-run` ONLY. It reads `docs/judgment/records/ledger.jsonl`,
|
|
7
|
+
* maps every decision-shaped entry through `lib/judgment-decisions.js`, and
|
|
8
|
+
* prints the mapping plus the value-spike numbers. It writes nothing, anywhere.
|
|
9
|
+
*
|
|
10
|
+
* The write path is P2 and the backfill is P3; both are blocked until the spike
|
|
11
|
+
* is reported against its pre-registered threshold, and the backfill is
|
|
12
|
+
* additionally blocked on the P2.5 inferred-conviction review gate (D4).
|
|
13
|
+
* Invoking this without `--dry-run` therefore refuses rather than doing
|
|
14
|
+
* something plausible.
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* node bin/judgment-migrate.js --dry-run [--json] [--review-batch]
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
21
|
+
import { join, resolve, dirname } from 'node:path';
|
|
22
|
+
|
|
23
|
+
import { mapLedger, applyReviewFile, CONFIDENCE } from '../lib/judgment-decisions.js';
|
|
24
|
+
import { createSmartmemoryClient } from '../lib/smartmemory-client.js';
|
|
25
|
+
import { getSmartmemoryConfig } from '../lib/smartmemory-config.js';
|
|
26
|
+
import { readSidecar, sidecarPath, writeJudgmentDecision } from '../lib/judgment-decision-write.js';
|
|
27
|
+
|
|
28
|
+
/** Pre-registered 2026-08-22, before any count was taken. */
|
|
29
|
+
const SPIKE_THRESHOLD_RULES = 10;
|
|
30
|
+
|
|
31
|
+
function readLedger(cwd) {
|
|
32
|
+
const path = join(cwd, 'docs', 'judgment', 'records', 'ledger.jsonl');
|
|
33
|
+
if (!existsSync(path)) {
|
|
34
|
+
throw new Error(`judgment-migrate: no ledger at ${path}`);
|
|
35
|
+
}
|
|
36
|
+
return readFileSync(path, 'utf8')
|
|
37
|
+
.split('\n')
|
|
38
|
+
.filter(Boolean)
|
|
39
|
+
.map((line, i) => {
|
|
40
|
+
try {
|
|
41
|
+
return JSON.parse(line);
|
|
42
|
+
} catch (err) {
|
|
43
|
+
throw new Error(`judgment-migrate: ledger line ${i + 1} is not JSON: ${err.message}`);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The owner's P2.5 verdicts. Absent, the dry run still reports (it writes
|
|
50
|
+
* nothing), but it says so — a report that silently skipped the gate would read
|
|
51
|
+
* exactly like one that passed it.
|
|
52
|
+
*/
|
|
53
|
+
function readReview(cwd) {
|
|
54
|
+
const path = join(cwd, 'docs', 'features', 'GOV-COMPOSE-SEAM-1', 'conviction-review.json');
|
|
55
|
+
if (!existsSync(path)) return null;
|
|
56
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function truncate(s, n) {
|
|
60
|
+
const one = (s ?? '').replace(/\s+/g, ' ').trim();
|
|
61
|
+
return one.length > n ? `${one.slice(0, n - 1)}…` : one;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function report(mapped, opts) {
|
|
65
|
+
const { decisions, skipped } = mapped;
|
|
66
|
+
|
|
67
|
+
const byKind = {};
|
|
68
|
+
for (const d of decisions) {
|
|
69
|
+
byKind[d.entry.kind] = (byKind[d.entry.kind] ?? 0) + 1;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const candidates = decisions.filter((d) => d.enforceability.verdict === 'candidate');
|
|
73
|
+
const inferred = decisions.filter((d) => d.decision.context_snapshot.conviction_review_required);
|
|
74
|
+
const reviewed = decisions.filter((d) => d.decision.context_snapshot.conviction_review);
|
|
75
|
+
|
|
76
|
+
if (opts.json) {
|
|
77
|
+
process.stdout.write(`${JSON.stringify({
|
|
78
|
+
total_entries: decisions.length + skipped.length,
|
|
79
|
+
decision_shaped: decisions.length,
|
|
80
|
+
by_kind: byKind,
|
|
81
|
+
skipped: skipped.length,
|
|
82
|
+
enforceable_candidates: candidates.length,
|
|
83
|
+
threshold: SPIKE_THRESHOLD_RULES,
|
|
84
|
+
inferred_conviction_review_required: inferred.length,
|
|
85
|
+
inferred_conviction_reviewed: reviewed.length,
|
|
86
|
+
review_ruled_at: opts.review?.ruled_at ?? null,
|
|
87
|
+
decisions: decisions.map((d) => ({
|
|
88
|
+
seq: d.seq,
|
|
89
|
+
kind: d.entry.kind,
|
|
90
|
+
decision: d.decision,
|
|
91
|
+
enforceability: d.enforceability,
|
|
92
|
+
})),
|
|
93
|
+
}, null, 2)}\n`);
|
|
94
|
+
return candidates.length;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const out = [];
|
|
98
|
+
out.push('# judgment-migrate --dry-run (GOV-COMPOSE-SEAM-1 canon-on-decisions P1)');
|
|
99
|
+
out.push('');
|
|
100
|
+
out.push('NOTHING WAS WRITTEN. This is the mapping and the spike measurement only.');
|
|
101
|
+
out.push('');
|
|
102
|
+
out.push(`Ledger entries: ${decisions.length + skipped.length}`);
|
|
103
|
+
out.push(`Decision-shaped: ${decisions.length} (${Object.entries(byKind).map(([k, v]) => `${k}=${v}`).join(' ')})`);
|
|
104
|
+
out.push(`Not decisions: ${skipped.length} (note/escalate/override/attest/calibrate — D3)`);
|
|
105
|
+
out.push('');
|
|
106
|
+
out.push('## Value spike (threshold pre-registered 2026-08-22)');
|
|
107
|
+
out.push('');
|
|
108
|
+
out.push(`Enforceable CANDIDATES: ${candidates.length} (threshold to ship consume-bundle: >= ${SPIKE_THRESHOLD_RULES})`);
|
|
109
|
+
out.push('');
|
|
110
|
+
out.push('A candidate is NOT a counted rule. The classifier fires when all three D5');
|
|
111
|
+
out.push('signals are present; the reported spike number is the ADJUDICATED count after a');
|
|
112
|
+
out.push('human confirms each candidate actually names a step, names an observable, and');
|
|
113
|
+
out.push('could be violated by a real build. Report the adjudicated number, not this one.');
|
|
114
|
+
out.push('');
|
|
115
|
+
out.push('## Enforceable candidates (adjudicate these)');
|
|
116
|
+
out.push('');
|
|
117
|
+
for (const d of candidates) {
|
|
118
|
+
const s = d.enforceability.signals;
|
|
119
|
+
out.push(`- [${d.seq}] ${d.entry.kind} / ${d.decision.decision_type} step=${s.namesStep} obs=${s.namesObservable} violable=${s.violable}`);
|
|
120
|
+
out.push(` ${truncate(d.decision.content, 100)}`);
|
|
121
|
+
}
|
|
122
|
+
out.push('');
|
|
123
|
+
out.push('## Inferred convictions (P2.5 review gate — D4)');
|
|
124
|
+
out.push('');
|
|
125
|
+
out.push(`Inferred scale: high=${CONFIDENCE.inferred.high} medium=${CONFIDENCE.inferred.medium} low=${CONFIDENCE.inferred.low}`);
|
|
126
|
+
out.push(`Stated scale: high=${CONFIDENCE.stated.high} medium=${CONFIDENCE.stated.medium} low=${CONFIDENCE.stated.low}`);
|
|
127
|
+
out.push('');
|
|
128
|
+
if (!opts.review) {
|
|
129
|
+
out.push(`${inferred.length} decision-shaped entries carry an agent-inferred conviction, and NO`);
|
|
130
|
+
out.push('verdict file was found. The backfill REFUSES these until the owner rules on each.');
|
|
131
|
+
for (const d of inferred) {
|
|
132
|
+
const c = d.decision.context_snapshot.conviction;
|
|
133
|
+
out.push(`- [${d.seq}] ${c.level} (inferred -> ${d.decision.confidence}) ${truncate(d.decision.content, 90)}`);
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
const byGroup = {};
|
|
137
|
+
for (const d of reviewed) {
|
|
138
|
+
const r = d.decision.context_snapshot.conviction_review;
|
|
139
|
+
byGroup[r.verdict] = (byGroup[r.verdict] ?? 0) + 1;
|
|
140
|
+
}
|
|
141
|
+
out.push(`RULED ${reviewed.length}/${reviewed.length} by ${opts.review.ruled_by} on ${opts.review.ruled_at}:`);
|
|
142
|
+
for (const [v, n] of Object.entries(byGroup)) out.push(` ${v}: ${n}`);
|
|
143
|
+
out.push('');
|
|
144
|
+
for (const d of reviewed) {
|
|
145
|
+
const snap = d.decision.context_snapshot;
|
|
146
|
+
const marks = [
|
|
147
|
+
snap.conviction_strength_dropped ? 'strength-dropped' : null,
|
|
148
|
+
snap.conviction_unrated ? 'UNRATED, not rule-eligible' : null,
|
|
149
|
+
].filter(Boolean).join(', ');
|
|
150
|
+
out.push(`- [${d.seq}] ${snap.conviction.level} ${snap.conviction_review.verdict} -> ${d.decision.confidence}/${d.decision.source_type}${marks ? ` (${marks})` : ''}`);
|
|
151
|
+
out.push(` ${truncate(d.decision.content, 90)}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (opts.reviewBatch) {
|
|
156
|
+
out.push('');
|
|
157
|
+
out.push('## Review batch (full text of each inferred entry)');
|
|
158
|
+
for (const d of (opts.review ? reviewed : inferred)) {
|
|
159
|
+
out.push('');
|
|
160
|
+
out.push(`### [${d.seq}] ${d.entry.title}`);
|
|
161
|
+
out.push(`conviction: ${d.decision.context_snapshot.conviction.level} (inferred)`);
|
|
162
|
+
out.push('');
|
|
163
|
+
out.push(d.entry.body ?? '(no body)');
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
out.push('');
|
|
168
|
+
process.stdout.write(`${out.join('\n')}\n`);
|
|
169
|
+
return candidates.length;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
// ── P3: the backfill ────────────────────────────────────────────────────────
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Resume state is the SHARED idempotency ledger in `lib/judgment-decision-write.js`
|
|
177
|
+
* (`docs/judgment/records/decision-ids.json`), NOT a private file.
|
|
178
|
+
*
|
|
179
|
+
* This backfill briefly kept its own under `.compose/data/`, keyed identically
|
|
180
|
+
* but read separately from the live write path. Same key, two ledgers, neither
|
|
181
|
+
* reading the other: a decision recorded live and then backfilled would be
|
|
182
|
+
* written twice, and the create endpoint has no server-side idempotency to
|
|
183
|
+
* catch it. That is the same "two mechanisms guarding one fact" failure D2
|
|
184
|
+
* retired the markdown hash chain over. Merged 2026-08-22; the old file is
|
|
185
|
+
* adopted on first read by `readSidecar` so a migration already run is not
|
|
186
|
+
* repeated.
|
|
187
|
+
*
|
|
188
|
+
* The per-write flush and the verify-before-skip both survive the merge — they
|
|
189
|
+
* moved INTO the shared writer, which is where the live path needed them too.
|
|
190
|
+
*/
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Resolve where the backfill writes.
|
|
194
|
+
*
|
|
195
|
+
* Flags override `.compose/compose.json#smartmemory`. Both a workspace and a
|
|
196
|
+
* key-env name are REQUIRED and there is no default: a backfill that guesses
|
|
197
|
+
* its destination can write 43 decisions into someone's working memory, and
|
|
198
|
+
* that is not recoverable by re-running it.
|
|
199
|
+
*/
|
|
200
|
+
function resolveTarget(cwd, argv) {
|
|
201
|
+
const flag = (name) => {
|
|
202
|
+
const i = argv.indexOf(name);
|
|
203
|
+
return i >= 0 ? argv[i + 1] : undefined;
|
|
204
|
+
};
|
|
205
|
+
const cfg = getSmartmemoryConfig(cwd);
|
|
206
|
+
const baseUrl = flag('--api-url') ?? cfg.baseUrl ?? process.env.SMARTMEMORY_API_URL;
|
|
207
|
+
const workspaceId = flag('--workspace') ?? cfg.workspaceId;
|
|
208
|
+
const apiKeyEnv = flag('--api-key-env') ?? cfg.apiKeyEnv;
|
|
209
|
+
|
|
210
|
+
const missing = [];
|
|
211
|
+
if (!baseUrl) missing.push('--api-url (or smartmemory.baseUrl)');
|
|
212
|
+
if (!workspaceId) missing.push('--workspace (or smartmemory.workspaceId)');
|
|
213
|
+
if (!apiKeyEnv) missing.push('--api-key-env (or smartmemory.apiKeyEnv)');
|
|
214
|
+
if (missing.length) {
|
|
215
|
+
throw new Error(
|
|
216
|
+
`judgment-migrate --apply: refusing to guess a destination. Missing ${missing.join(', ')}.`,
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
if (!process.env[apiKeyEnv]) {
|
|
220
|
+
throw new Error(`judgment-migrate --apply: $${apiKeyEnv} is not set`);
|
|
221
|
+
}
|
|
222
|
+
// `enabled: true` is the flags themselves. The config gate exists so the
|
|
223
|
+
// coupling is OFF by default for the live tool path; naming a destination
|
|
224
|
+
// explicitly on an `--apply` invocation IS the opt-in, and requiring the
|
|
225
|
+
// operator to also edit compose.json first would mean a one-off migration
|
|
226
|
+
// could not run without turning the live emitter on for every later build.
|
|
227
|
+
return { enabled: true, baseUrl, workspaceId, apiKeyEnv, timeoutMs: 15000 };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** The payload the HTTP contract accepts. Anything the mapper carries that the
|
|
231
|
+
* route has no field for rides in `context_snapshot`, never silently dropped. */
|
|
232
|
+
function toCreateBody(d) {
|
|
233
|
+
return {
|
|
234
|
+
content: d.content,
|
|
235
|
+
decision_type: d.decision_type,
|
|
236
|
+
confidence: d.confidence,
|
|
237
|
+
domain: d.domain,
|
|
238
|
+
tags: d.tags,
|
|
239
|
+
rejected_alternatives: d.rejected_alternatives,
|
|
240
|
+
rationale: d.rationale,
|
|
241
|
+
source_type: d.source_type,
|
|
242
|
+
context_snapshot: d.context_snapshot,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function backfill(cwd, mapped, target, opts) {
|
|
247
|
+
const client = createSmartmemoryClient(target);
|
|
248
|
+
const written = [];
|
|
249
|
+
const skipped = [];
|
|
250
|
+
const failed = [];
|
|
251
|
+
|
|
252
|
+
for (const row of mapped.decisions) {
|
|
253
|
+
const d = row.decision;
|
|
254
|
+
const key = d.idempotency_key;
|
|
255
|
+
try {
|
|
256
|
+
// One write path for the live tool call and the bulk migration. It owns
|
|
257
|
+
// the shared ledger, the verify-before-skip, the provenance check and the
|
|
258
|
+
// per-write flush; this loop owns only batching and reporting.
|
|
259
|
+
const res = await writeJudgmentDecision(cwd, d, { client, config: target });
|
|
260
|
+
if (res === null) {
|
|
261
|
+
failed.push({ seq: row.seq, key, stage: 'config', error: 'smartmemory coupling is disabled' });
|
|
262
|
+
if (opts.failFast) break;
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
(res.skipped ? skipped : written).push({ seq: row.seq, key, decision_id: res.decision_id });
|
|
266
|
+
} catch (err) {
|
|
267
|
+
// Fail-closed. A dropped decision silently un-governs a build, so the
|
|
268
|
+
// backfill reports and stops counting it as done rather than warning on.
|
|
269
|
+
failed.push({ seq: row.seq, key, stage: 'create', error: err.message });
|
|
270
|
+
if (opts.failFast) break;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return { written, skipped, failed, state: { written: readSidecar(cwd) } };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* The `correct` -> supersede half of D3, which this backfill CANNOT complete.
|
|
279
|
+
*
|
|
280
|
+
* `POST /memory/decisions/{id}/supersede` mints its replacement from
|
|
281
|
+
* `new_content` / `new_decision_type` / `new_confidence` alone — it accepts no
|
|
282
|
+
* `source_type`, no `context_snapshot`, no tags. Calling it after writing the
|
|
283
|
+
* 43 would produce a 44th, 45th and 46th decision with no idempotency key and
|
|
284
|
+
* no provenance, and would break the "run it twice, get 43" property.
|
|
285
|
+
*
|
|
286
|
+
* So the link is recorded and reported, not faked. Closing it needs a service
|
|
287
|
+
* change (supersede taking an existing decision id, or create taking
|
|
288
|
+
* `supersedes`), which is a separate piece of work.
|
|
289
|
+
*/
|
|
290
|
+
function pendingSupersedes(mapped) {
|
|
291
|
+
return mapped.decisions
|
|
292
|
+
.filter((r) => r.decision.supersedes_slug)
|
|
293
|
+
.map((r) => ({ seq: r.seq, slug: r.decision.supersedes_slug, content: r.decision.content }));
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function writeReport(cwd, result, mapped, target) {
|
|
297
|
+
const pend = pendingSupersedes(mapped);
|
|
298
|
+
const lines = [];
|
|
299
|
+
lines.push('# GOV-COMPOSE-SEAM-1 `canon-on-decisions` P3 — backfill report');
|
|
300
|
+
lines.push('');
|
|
301
|
+
lines.push(`**Target workspace:** \`${target.workspaceId}\` · **API:** \`${target.baseUrl}\``);
|
|
302
|
+
lines.push('');
|
|
303
|
+
lines.push(`- Decision-shaped ledger entries: **${mapped.decisions.length}**`);
|
|
304
|
+
lines.push(`- Written this run: **${result.written.length}**`);
|
|
305
|
+
lines.push(`- Already present (verified server-side, skipped): **${result.skipped.length}**`);
|
|
306
|
+
lines.push(`- Failed: **${result.failed.length}**`);
|
|
307
|
+
lines.push('');
|
|
308
|
+
if (result.failed.length) {
|
|
309
|
+
lines.push('## Failures');
|
|
310
|
+
lines.push('');
|
|
311
|
+
for (const f of result.failed) lines.push(`- [${f.seq}] ${f.stage}: ${f.error}`);
|
|
312
|
+
lines.push('');
|
|
313
|
+
}
|
|
314
|
+
lines.push('## Superseding links NOT applied');
|
|
315
|
+
lines.push('');
|
|
316
|
+
lines.push('`POST /memory/decisions/{id}/supersede` builds its replacement from `new_content`,');
|
|
317
|
+
lines.push('`new_decision_type` and `new_confidence` only — no `source_type`, no');
|
|
318
|
+
lines.push('`context_snapshot`, no tags. Using it here would mint extra decisions with no');
|
|
319
|
+
lines.push('idempotency key and break the run-it-twice property, so these links are recorded');
|
|
320
|
+
lines.push('and left unapplied rather than faked. Closing them needs a service change.');
|
|
321
|
+
lines.push('');
|
|
322
|
+
for (const p of pend) lines.push(`- [${p.seq}] supersedes \`${p.slug}\` — ${p.content}`);
|
|
323
|
+
lines.push('');
|
|
324
|
+
const out = join(cwd, 'docs', 'features', 'GOV-COMPOSE-SEAM-1', 'migration-report.md');
|
|
325
|
+
mkdirSync(dirname(out), { recursive: true });
|
|
326
|
+
writeFileSync(out, `${lines.join('\n')}\n`);
|
|
327
|
+
return out;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async function main(argv) {
|
|
331
|
+
const opts = {
|
|
332
|
+
dryRun: argv.includes('--dry-run'),
|
|
333
|
+
apply: argv.includes('--apply'),
|
|
334
|
+
json: argv.includes('--json'),
|
|
335
|
+
reviewBatch: argv.includes('--review-batch'),
|
|
336
|
+
failFast: !argv.includes('--keep-going'),
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
if (!opts.dryRun && !opts.apply) {
|
|
340
|
+
process.stderr.write(
|
|
341
|
+
'judgment-migrate: pass --dry-run (report only, writes nothing) or --apply (backfill).\n'
|
|
342
|
+
+ 'Refusing rather than doing something plausible.\n',
|
|
343
|
+
);
|
|
344
|
+
return 2;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const cwd = resolve(process.env.COMPOSE_CWD ?? process.cwd());
|
|
348
|
+
const events = readLedger(cwd);
|
|
349
|
+
const mapped = mapLedger(events);
|
|
350
|
+
|
|
351
|
+
// The P2.5 gate runs BEFORE anything else, in both modes. It throws on the
|
|
352
|
+
// first inferred conviction the owner has not ruled on, so the refusal shows
|
|
353
|
+
// up before a single write rather than halfway through a backfill.
|
|
354
|
+
opts.review = readReview(cwd);
|
|
355
|
+
if (opts.review) applyReviewFile(mapped, opts.review);
|
|
356
|
+
|
|
357
|
+
if (opts.dryRun) {
|
|
358
|
+
report(mapped, opts);
|
|
359
|
+
return 0;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// --apply
|
|
363
|
+
if (!opts.review) {
|
|
364
|
+
process.stderr.write(
|
|
365
|
+
'judgment-migrate --apply: no conviction-review.json found. The P2.5 gate has not been\n'
|
|
366
|
+
+ 'ruled, so the inferred convictions cannot be written. Refusing.\n',
|
|
367
|
+
);
|
|
368
|
+
return 2;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const target = resolveTarget(cwd, argv);
|
|
372
|
+
const result = await backfill(cwd, mapped, target, opts);
|
|
373
|
+
const reportPath = writeReport(cwd, result, mapped, target);
|
|
374
|
+
|
|
375
|
+
process.stdout.write(
|
|
376
|
+
`judgment-migrate --apply -> ${target.workspaceId}\n`
|
|
377
|
+
+ ` written: ${result.written.length} skipped(verified): ${result.skipped.length} `
|
|
378
|
+
+ `failed: ${result.failed.length}\n`
|
|
379
|
+
+ ` report: ${reportPath}\n`,
|
|
380
|
+
);
|
|
381
|
+
return result.failed.length ? 1 : 0;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
main(process.argv.slice(2)).then(
|
|
385
|
+
(code) => { process.exitCode = code; },
|
|
386
|
+
(err) => { process.stderr.write(`judgment-migrate: ${err.message}\n`); process.exitCode = 1; },
|
|
387
|
+
);
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
3
|
"$id": "https://forge.local/contracts/comp-obs-contract.schema.json",
|
|
4
4
|
"title": "COMP-OBS-CONTRACT — Wave 6 shared data model",
|
|
5
|
-
"version": "0.2.
|
|
5
|
+
"version": "0.2.7",
|
|
6
6
|
"_source": "docs/features/COMP-OBS-CONTRACT/schema.json",
|
|
7
7
|
"_roadmap": "Wave 6 — Situational Awareness",
|
|
8
8
|
"_changelog": {
|
|
9
|
+
"0.2.7": "COMP-LIFECYCLE-BACKFILL (2026-09-05) — DecisionEvent kind=phase_transition metadata gains three OPTIONAL fields (origin, recorded_at, confidence). additionalProperties:false is kept; required stays [from_phase, to_phase], so every 0.2.6 event still validates.",
|
|
10
|
+
"0.2.6": "COMP-POLICY-CHECK (2026-08-17) — DecisionEvent gains kind=policy_violation (pre-response policy check, emitter server/decision-event-emit.js buildPolicyViolationEvent) with a closed metadata subschema {step_id, rule, matched, suppressed, user_mode, build_id}. Additive — 0.2.5 events continue to validate. Note: BuildStreamEvent.schema_version const stays 0.2.5 (no BuildStreamEvent change in this bump).",
|
|
9
11
|
"0.2.5": "STRAT-PAR-STREAM — added BuildStreamEvent typed discriminated union (12 kinds): 6 new agent-narration kinds (agent_started, tool_use_summary, agent_relay closed + live; iteration_update, tier_result, health_event closed + reserved) and 6 legacy build-level kinds imported from compose/lib/build-stream-writer.js (capability_profile, capability_violation, step_usage, gate_tier_result, health_score, build_end — open metadata, catalog only; STRAT-PAR-STREAM-LEGACY-CLOSE will tighten). Additive — v0.2.4 specs continue to validate.",
|
|
10
12
|
"0.2.4": "COMP-OBS-DRIFT — added optional breach_started_at + breach_event_id to DriftAxis for stable threshold-event identity across WS reconnect; live emit and snapshot rehydration both compose the event id from these persisted fields.",
|
|
11
13
|
"0.2.3": "Codex review (2026-04-24) — two joinable-data fixes + SURFACE-split propagation. (a) `StatusSnapshot.drift_alerts[]` now a locally-defined subschema that mandates `breached: true`, so STATUS cannot emit non-alert axes through the alerts field. (b) gate `DecisionEvent.metadata.gate_log_entry_id` promoted to required — every gate DecisionEvent MUST carry its GateLogEntry id. `GateLogEntry.decision_event_id` remains nullable (explicit emission-failure escape hatch, reader reconciliation documented in prose). (c) Propagate 2026-04-23 SURFACE split (ROADMAP): `COMP-OBS-SURFACE` → `COMP-OBS-TIMELINE` (region ②, DecisionEvent producer+reader) + `COMP-OBS-STEPDETAIL` (step-level surface, no direct contract consumption in v1). `_consumers` updated; no field-shape changes beyond (a)+(b). DecisionEvent emitter ownership restated: BRANCH emits kind=branch, GATELOG emits kind=gate, TIMELINE adds kind=phase_transition (on lifecycle.advance) + kind=iteration (wrapping existing iteration-loop events), DRIFT emits kind=drift_threshold on breach.",
|
|
@@ -216,7 +218,7 @@
|
|
|
216
218
|
"id": { "type": "string", "format": "uuid" },
|
|
217
219
|
"feature_code": { "type": "string" },
|
|
218
220
|
"timestamp": { "type": "string", "format": "date-time" },
|
|
219
|
-
"kind": { "type": "string", "enum": ["gate", "branch", "iteration", "phase_transition", "drift_threshold"] },
|
|
221
|
+
"kind": { "type": "string", "enum": ["gate", "branch", "iteration", "phase_transition", "drift_threshold", "policy_violation"] },
|
|
220
222
|
"title": { "type": "string", "maxLength": 140 },
|
|
221
223
|
"roles": {
|
|
222
224
|
"type": "array",
|
|
@@ -264,11 +266,15 @@
|
|
|
264
266
|
},
|
|
265
267
|
{
|
|
266
268
|
"if": { "properties": { "kind": { "const": "phase_transition" } } },
|
|
267
|
-
"then": { "properties": { "metadata": { "type": "object", "required": ["from_phase", "to_phase"], "properties": { "from_phase": { "type": "string" }, "to_phase": { "type": "string" } }, "additionalProperties": false } } }
|
|
269
|
+
"then": { "properties": { "metadata": { "type": "object", "required": ["from_phase", "to_phase"], "properties": { "from_phase": { "type": "string" }, "to_phase": { "type": "string" }, "origin": { "type": "string", "enum": ["live", "backfill"], "description": "ABSENT means live; emitters pass stored provenance unchanged." }, "recorded_at": { "type": "string", "format": "date-time" }, "confidence": { "type": "number", "minimum": 0, "maximum": 1 } }, "additionalProperties": false } } }
|
|
268
270
|
},
|
|
269
271
|
{
|
|
270
272
|
"if": { "properties": { "kind": { "const": "drift_threshold" } } },
|
|
271
273
|
"then": { "properties": { "metadata": { "type": "object", "required": ["axis_id", "ratio", "threshold"], "properties": { "axis_id": { "type": "string", "enum": ["path_drift", "contract_drift", "review_debt_drift"] }, "ratio": { "type": "number" }, "threshold": { "type": "number" } }, "additionalProperties": false } } }
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
"if": { "properties": { "kind": { "const": "policy_violation" } } },
|
|
277
|
+
"then": { "properties": { "metadata": { "type": "object", "required": ["step_id", "rule", "matched", "suppressed", "user_mode"], "properties": { "step_id": { "type": "string" }, "rule": { "type": "string" }, "matched": { "type": "string" }, "suppressed": { "type": "boolean" }, "user_mode": { "type": "string", "enum": ["AUTONOMOUS", "PACED", "SKILL_GATED"] }, "build_id": { "type": ["string", "null"] } }, "additionalProperties": false } } }
|
|
272
278
|
}
|
|
273
279
|
]
|
|
274
280
|
},
|
|
@@ -40,6 +40,11 @@
|
|
|
40
40
|
"maximum": 4,
|
|
41
41
|
"description": "COMP-TRIAGE-5: raw machine tier (0-4) from the front estimator/refinement. Maps to `complexity` via tierToComplexity."
|
|
42
42
|
},
|
|
43
|
+
"triageConfidence": {
|
|
44
|
+
"type": "string",
|
|
45
|
+
"enum": ["high", "medium", "low"],
|
|
46
|
+
"description": "COMP-TRIAGE-6: confidence from the front scope estimate."
|
|
47
|
+
},
|
|
43
48
|
"estimateSource": {
|
|
44
49
|
"type": "string",
|
|
45
50
|
"enum": ["front", "refined", "escalated"],
|