instar 1.3.269 → 1.3.270

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 (81) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +9 -1
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/coordination/MandateAudit.d.ts +48 -0
  5. package/dist/coordination/MandateAudit.d.ts.map +1 -0
  6. package/dist/coordination/MandateAudit.js +95 -0
  7. package/dist/coordination/MandateAudit.js.map +1 -0
  8. package/dist/coordination/MandateGate.d.ts +48 -0
  9. package/dist/coordination/MandateGate.d.ts.map +1 -0
  10. package/dist/coordination/MandateGate.js +104 -0
  11. package/dist/coordination/MandateGate.js.map +1 -0
  12. package/dist/coordination/MandateStore.d.ts +60 -0
  13. package/dist/coordination/MandateStore.d.ts.map +1 -0
  14. package/dist/coordination/MandateStore.js +106 -0
  15. package/dist/coordination/MandateStore.js.map +1 -0
  16. package/dist/coordination/conditions.d.ts +32 -0
  17. package/dist/coordination/conditions.d.ts.map +1 -0
  18. package/dist/coordination/conditions.js +54 -0
  19. package/dist/coordination/conditions.js.map +1 -0
  20. package/dist/coordination/types.d.ts +74 -0
  21. package/dist/coordination/types.d.ts.map +1 -0
  22. package/dist/coordination/types.js +17 -0
  23. package/dist/coordination/types.js.map +1 -0
  24. package/dist/core/ApprovalLedger.d.ts +132 -0
  25. package/dist/core/ApprovalLedger.d.ts.map +1 -0
  26. package/dist/core/ApprovalLedger.js +191 -0
  27. package/dist/core/ApprovalLedger.js.map +1 -0
  28. package/dist/core/SessionBuildContextStore.d.ts +33 -0
  29. package/dist/core/SessionBuildContextStore.d.ts.map +1 -0
  30. package/dist/core/SessionBuildContextStore.js +107 -0
  31. package/dist/core/SessionBuildContextStore.js.map +1 -0
  32. package/dist/core/SessionManager.d.ts +4 -0
  33. package/dist/core/SessionManager.d.ts.map +1 -1
  34. package/dist/core/SessionManager.js +44 -3
  35. package/dist/core/SessionManager.js.map +1 -1
  36. package/dist/core/types.d.ts +11 -0
  37. package/dist/core/types.d.ts.map +1 -1
  38. package/dist/core/types.js.map +1 -1
  39. package/dist/feedback-factory/migration/immutableGuard.d.ts +87 -0
  40. package/dist/feedback-factory/migration/immutableGuard.d.ts.map +1 -0
  41. package/dist/feedback-factory/migration/immutableGuard.js +163 -0
  42. package/dist/feedback-factory/migration/immutableGuard.js.map +1 -0
  43. package/dist/feedback-factory/migration/importIntegrity.d.ts +112 -0
  44. package/dist/feedback-factory/migration/importIntegrity.d.ts.map +1 -0
  45. package/dist/feedback-factory/migration/importIntegrity.js +202 -0
  46. package/dist/feedback-factory/migration/importIntegrity.js.map +1 -0
  47. package/dist/feedback-factory/monitor/parityMonitor.d.ts +75 -0
  48. package/dist/feedback-factory/monitor/parityMonitor.d.ts.map +1 -0
  49. package/dist/feedback-factory/monitor/parityMonitor.js +98 -0
  50. package/dist/feedback-factory/monitor/parityMonitor.js.map +1 -0
  51. package/dist/feedback-factory/monitor/parityMonitorStore.d.ts +44 -0
  52. package/dist/feedback-factory/monitor/parityMonitorStore.d.ts.map +1 -0
  53. package/dist/feedback-factory/monitor/parityMonitorStore.js +80 -0
  54. package/dist/feedback-factory/monitor/parityMonitorStore.js.map +1 -0
  55. package/dist/scaffold/templates.d.ts.map +1 -1
  56. package/dist/scaffold/templates.js +11 -0
  57. package/dist/scaffold/templates.js.map +1 -1
  58. package/dist/server/AgentServer.d.ts +6 -0
  59. package/dist/server/AgentServer.d.ts.map +1 -1
  60. package/dist/server/AgentServer.js +94 -1
  61. package/dist/server/AgentServer.js.map +1 -1
  62. package/dist/server/CapabilityIndex.d.ts.map +1 -1
  63. package/dist/server/CapabilityIndex.js +29 -0
  64. package/dist/server/CapabilityIndex.js.map +1 -1
  65. package/dist/server/routes.d.ts +11 -0
  66. package/dist/server/routes.d.ts.map +1 -1
  67. package/dist/server/routes.js +255 -0
  68. package/dist/server/routes.js.map +1 -1
  69. package/package.json +1 -1
  70. package/src/data/builtin-manifest.json +48 -48
  71. package/src/scaffold/templates.ts +11 -0
  72. package/upgrades/1.3.270.md +106 -0
  73. package/upgrades/approval-ledger-phase2.eli16.md +56 -0
  74. package/upgrades/feedback-phase3-monitor.eli16.md +51 -0
  75. package/upgrades/feedback-phase4-integrity.eli16.md +54 -0
  76. package/upgrades/respawn-build-context-reestablishment.eli16.md +5 -0
  77. package/upgrades/side-effects/approval-ledger-phase2.md +82 -0
  78. package/upgrades/side-effects/coordination-mandate-enforcement.md +86 -0
  79. package/upgrades/side-effects/feedback-phase3-monitor.md +37 -0
  80. package/upgrades/side-effects/feedback-phase4-integrity.md +35 -0
  81. package/upgrades/side-effects/respawn-build-context-reestablishment.md +81 -0
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Coordination Mandate — shared types.
3
+ *
4
+ * A Coordination Mandate is a human-authored, signed, bounded, conditioned,
5
+ * revocable, audited delegation of SPECIFIC authorities to a SPECIFIC pair of
6
+ * agents for a SPECIFIC purpose and bounded time. It moves the human's
7
+ * authorization from per-action to standing-policy WITHOUT removing the human as
8
+ * the authorizer — preserving `requester ≠ authorizer` (the agent is the
9
+ * requester; the human-authored mandate is the authorizer).
10
+ *
11
+ * Spec: docs/specs/coordination-mandate.md (§4 concrete shape). The first mandate's
12
+ * resolved bounds (Justin's A/A/B sign-off, 2026-06-05): cutover stays his manual
13
+ * click; issuance is dashboard-PIN-gated; the first mandate carries only authorities
14
+ * 1–2 (exchange-read-credential + sign-code-review), NOT execute-cutover.
15
+ */
16
+ /** A single delegated authority within a mandate. */
17
+ export interface Authority {
18
+ /** The action this authority permits, e.g. 'exchange-read-credential' | 'sign-code-review' | 'execute-cutover'. */
19
+ action: string;
20
+ /** Explicit bounds the action's params must satisfy (exact-match per key). */
21
+ bounds: Record<string, unknown>;
22
+ /** Optional objective gate that must evaluate true before the action is allowed,
23
+ * e.g. 'integrity-gate-pass' | 'parity-zero-divergence'. Resolved from REAL state,
24
+ * never from an agent's assertion. */
25
+ requiresCondition?: string;
26
+ }
27
+ /** The human-authored delegation. `authProof` covers all other fields. */
28
+ export interface CoordinationMandate {
29
+ id: string;
30
+ /** e.g. 'feedback-migration'. */
31
+ scope: string;
32
+ /** The two agents bound by the mandate, by routing fingerprint. */
33
+ agents: [string, string];
34
+ authorities: Authority[];
35
+ /** Always the human author identifier (e.g. 'justin'). */
36
+ author: string;
37
+ createdAt: string;
38
+ expiresAt: string;
39
+ /** Set when revoked; checked on every action. */
40
+ revoked: {
41
+ at: string;
42
+ reason: string;
43
+ } | null;
44
+ /** Authorship proof — an HMAC over the canonical (proof-excluded) mandate bytes,
45
+ * produced ONLY by the PIN-gated issuance path. An agent cannot mint or widen its
46
+ * own mandate without it; a forged/edited mandate fails verification. */
47
+ authProof: string;
48
+ }
49
+ export type MandateDecision = 'allow' | 'deny';
50
+ /** The input to a gate evaluation — what the agent is asking to do. */
51
+ export interface MandateEvaluation {
52
+ action: string;
53
+ params: Record<string, unknown>;
54
+ /** The calling agent's routing fingerprint (must be a named party). */
55
+ agentFp: string;
56
+ mandateId: string;
57
+ }
58
+ /** A single audited gate decision (one row of the hash-chained audit). */
59
+ export interface MandateAuditEntry {
60
+ ts: string;
61
+ mandateId: string;
62
+ agentFp: string;
63
+ action: string;
64
+ decision: MandateDecision;
65
+ /** Human-readable reason — which check passed/failed. */
66
+ reason: string;
67
+ /** The condition result when a `requiresCondition` was evaluated (else null). */
68
+ conditionResult: boolean | null;
69
+ /** Hash of the previous entry (genesis = ''), making the log tamper-evident (T8). */
70
+ prevHash: string;
71
+ /** sha256(prevHash + canonical(this-entry-without-hash)). */
72
+ hash: string;
73
+ }
74
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/coordination/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,qDAAqD;AACrD,MAAM,WAAW,SAAS;IACxB,mHAAmH;IACnH,MAAM,EAAE,MAAM,CAAC;IACf,8EAA8E;IAC9E,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC;;2CAEuC;IACvC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,0EAA0E;AAC1E,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,iCAAiC;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzB,WAAW,EAAE,SAAS,EAAE,CAAC;IACzB,0DAA0D;IAC1D,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,iDAAiD;IACjD,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC/C;;8EAE0E;IAC1E,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,MAAM,CAAC;AAE/C,uEAAuE;AACvE,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,uEAAuE;IACvE,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,0EAA0E;AAC1E,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,eAAe,CAAC;IAC1B,yDAAyD;IACzD,MAAM,EAAE,MAAM,CAAC;IACf,iFAAiF;IACjF,eAAe,EAAE,OAAO,GAAG,IAAI,CAAC;IAChC,qFAAqF;IACrF,QAAQ,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;CACd"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Coordination Mandate — shared types.
3
+ *
4
+ * A Coordination Mandate is a human-authored, signed, bounded, conditioned,
5
+ * revocable, audited delegation of SPECIFIC authorities to a SPECIFIC pair of
6
+ * agents for a SPECIFIC purpose and bounded time. It moves the human's
7
+ * authorization from per-action to standing-policy WITHOUT removing the human as
8
+ * the authorizer — preserving `requester ≠ authorizer` (the agent is the
9
+ * requester; the human-authored mandate is the authorizer).
10
+ *
11
+ * Spec: docs/specs/coordination-mandate.md (§4 concrete shape). The first mandate's
12
+ * resolved bounds (Justin's A/A/B sign-off, 2026-06-05): cutover stays his manual
13
+ * click; issuance is dashboard-PIN-gated; the first mandate carries only authorities
14
+ * 1–2 (exchange-read-credential + sign-code-review), NOT execute-cutover.
15
+ */
16
+ export {};
17
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/coordination/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG"}
@@ -0,0 +1,132 @@
1
+ /**
2
+ * ApprovalLedger — the durable, signed, append-only record of every operator
3
+ * approval decision, and the per-class agreement ratios computed from it.
4
+ *
5
+ * This is Phase 2 of the approved "Approval-as-Data" spec
6
+ * (`docs/specs/AUTONOMOUS-OPERATION-JUDGMENT-AND-APPROVAL-AS-DATA-SPEC.md`, Part B):
7
+ * an approval stops being a one-shot "approved" with no memory and becomes data —
8
+ * recorded as `approved-as-is` vs `approved-with-change` vs `rejected`, with the
9
+ * WHY of each divergence, so we can (a) see per decision-class where the operator
10
+ * agrees with the agent's recommendation as-is vs revises it, (b) trend toward
11
+ * approved-as-is by folding recurring divergences back into design guidance, and
12
+ * (c) — once a class's ratio holds — pilot auto-approval for that class (Phase 3).
13
+ *
14
+ * SCOPE (operator extension, 2026-06-05): the original spec tracked only official
15
+ * SPEC sign-offs. Justin extended it to track ALL approvals wherever they occur —
16
+ * a spec sign-off, a decision presented and approved in chat, or any other surface.
17
+ * Hence `subject`/`decisionClass`/`surface` (general) rather than `specSlug`/
18
+ * `specClass`, and the spec-only fields (`reviewIterations`/`commitSha`/`evidenceRef`)
19
+ * are optional — a chat approval simply omits them.
20
+ *
21
+ * Mirrors `SessionPoolE2EResultStore` exactly (signed, append-only, tamper-evident,
22
+ * torn-line-tolerant). The store is signer-agnostic — `sign`/`verifySig` are injected
23
+ * — so tests use an Ed25519 keypair and production uses an HMAC over the state secret.
24
+ *
25
+ * AUTHORITY INVARIANT (spec B1, load-bearing for correctness — NOT just integrity):
26
+ * for a MANUAL decision the OPERATOR is the authoritative source of `mode` and
27
+ * `divergences`. The agent must NEVER self-classify the operator's intent. The
28
+ * writer passes the operator-sourced classification (from the approve control / an
29
+ * explicit "as-is" vs "with changes: …"); this store records it faithfully. HMAC/
30
+ * signature guarantees integrity, not correctness — the authoritative-source rule
31
+ * is what prevents an agent inflating its own agreement ratio. An operator can
32
+ * dispute and CORRECT any row: a correction is itself an appended, signed row
33
+ * (`corrects` set), never an in-place edit — history preserved.
34
+ */
35
+ export type ApprovalMode = 'approved-as-is' | 'approved-with-change' | 'rejected';
36
+ /** Where the approval happened (operator-extension axis — "wherever they occur"). */
37
+ export type ApprovalSurface = 'spec' | 'chat' | 'other';
38
+ export declare const APPROVAL_SURFACES: readonly ApprovalSurface[];
39
+ /** Divergence taxonomy (spec B2). `missing-principle` is a candidate trigger to
40
+ * propose a new design principle — a PROPOSAL only, never auto-merged (Part C). */
41
+ export type DivergenceCategory = 'missing-principle' | 'risk-reduction' | 'scope-correction' | 'efficiency' | 'new-information' | 'style';
42
+ export declare const DIVERGENCE_CATEGORIES: readonly DivergenceCategory[];
43
+ export interface Divergence {
44
+ category: DivergenceCategory;
45
+ /** One-line what-changed. */
46
+ summary: string;
47
+ /** Why the operator changed it — the signal we fold back into design guidance. */
48
+ why: string;
49
+ }
50
+ export interface ApprovalRecord {
51
+ /** What was approved — a spec slug, or a stable id for a chat recommendation. */
52
+ subject: string;
53
+ /** The bucket the ratio is computed per (e.g. 'governance-safety', 'design-decision'). */
54
+ decisionClass: string;
55
+ /** Where the approval occurred. */
56
+ surface: ApprovalSurface;
57
+ decidedAt: string;
58
+ /** 'justin' for a manual decision; 'auto' for an auto-approval (Phase 3). */
59
+ approver: string;
60
+ mode: ApprovalMode;
61
+ /** Empty when approved-as-is; one entry per operator-stated change otherwise. */
62
+ divergences: Divergence[];
63
+ /** Spec-context fields — optional (a chat approval omits them). */
64
+ reviewIterations?: number;
65
+ commitSha?: string;
66
+ evidenceRef?: string;
67
+ /** Set when this row CORRECTS a prior row (operator dispute) — the prior subject+decidedAt. */
68
+ corrects?: string;
69
+ signature: string;
70
+ }
71
+ export type ApprovalRecordInput = Omit<ApprovalRecord, 'signature' | 'decidedAt' | 'divergences'> & {
72
+ decidedAt?: string;
73
+ divergences?: Divergence[];
74
+ };
75
+ export interface ApprovalLedgerDeps {
76
+ /** Absolute path to the append-only ledger file. */
77
+ filePath: string;
78
+ /** Sign the canonical (signature-excluded) row. Production: HMAC over the state secret. */
79
+ sign: (canonical: string) => string;
80
+ /** Verify a signature against the canonical row. */
81
+ verifySig: (canonical: string, signature: string) => boolean;
82
+ now?: () => number;
83
+ }
84
+ /** Eligibility knobs for `autoApprovalEligible` (a read-only SIGNAL; never gates here). */
85
+ export interface EligibilityPolicy {
86
+ /** Minimum approved-as-is ratio for a class to be auto-approval-eligible. */
87
+ minRatio: number;
88
+ /** Minimum CURRENT consecutive approved-as-is streak. */
89
+ minStreak: number;
90
+ /** Minimum total decisions in the class (don't qualify on a tiny sample). */
91
+ minTotal: number;
92
+ }
93
+ export declare const DEFAULT_ELIGIBILITY: EligibilityPolicy;
94
+ export interface ClassSummary {
95
+ decisionClass: string;
96
+ total: number;
97
+ approvedAsIs: number;
98
+ approvedWithChange: number;
99
+ rejected: number;
100
+ /** approvedAsIs / total (0 when total is 0). */
101
+ ratio: number;
102
+ /** CURRENT consecutive approved-as-is run (most recent rows), reset to 0 by any
103
+ * approved-with-change or rejected (spec B3). */
104
+ streak: number;
105
+ autoApprovalEligible: boolean;
106
+ /** Aggregate divergence-category counts for the class (drives the Phase-3 digest). */
107
+ divergenceCounts: Record<DivergenceCategory, number>;
108
+ }
109
+ /** Canonical bytes a row's signature covers — field-ordered, signature EXCLUDED.
110
+ * Optional fields are normalized so a present-vs-absent field is unambiguous, and
111
+ * divergences are flattened deterministically so reordering is detectable. */
112
+ export declare function canonicalApprovalRow(r: Omit<ApprovalRecord, 'signature'>): string;
113
+ export declare class ApprovalLedger {
114
+ private readonly d;
115
+ constructor(deps: ApprovalLedgerDeps);
116
+ /**
117
+ * Append a signed approval row. The `mode` + `divergences` MUST be operator-sourced
118
+ * (the authority invariant above) — this method does not infer them.
119
+ * Throws on an internally-inconsistent row so a malformed record can't pollute ratios.
120
+ */
121
+ recordApproval(input: ApprovalRecordInput): ApprovalRecord;
122
+ /** All rows (oldest→newest). Tolerant of a missing file / a torn trailing line. */
123
+ all(): ApprovalRecord[];
124
+ /** Verify a row's signature (tamper check). */
125
+ verify(row: ApprovalRecord): boolean;
126
+ /** Per-class agreement summary. Read-only; never gates by itself (spec B3). */
127
+ summarize(policy?: EligibilityPolicy): ClassSummary[];
128
+ /** Summary for one class (returns a zeroed summary if the class has no rows). */
129
+ summaryForClass(decisionClass: string, policy?: EligibilityPolicy): ClassSummary;
130
+ private summarizeRows;
131
+ }
132
+ //# sourceMappingURL=ApprovalLedger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ApprovalLedger.d.ts","sourceRoot":"","sources":["../../src/core/ApprovalLedger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAKH,MAAM,MAAM,YAAY,GAAG,gBAAgB,GAAG,sBAAsB,GAAG,UAAU,CAAC;AAElF,qFAAqF;AACrF,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAExD,eAAO,MAAM,iBAAiB,EAAE,SAAS,eAAe,EAAuC,CAAC;AAEhG;oFACoF;AACpF,MAAM,MAAM,kBAAkB,GAC1B,mBAAmB,GACnB,gBAAgB,GAChB,kBAAkB,GAClB,YAAY,GACZ,iBAAiB,GACjB,OAAO,CAAC;AAEZ,eAAO,MAAM,qBAAqB,EAAE,SAAS,kBAAkB,EAGrD,CAAC;AAEX,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,6BAA6B;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,kFAAkF;IAClF,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,cAAc;IAC7B,iFAAiF;IACjF,OAAO,EAAE,MAAM,CAAC;IAChB,0FAA0F;IAC1F,aAAa,EAAE,MAAM,CAAC;IACtB,mCAAmC;IACnC,OAAO,EAAE,eAAe,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,YAAY,CAAC;IACnB,iFAAiF;IACjF,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,mEAAmE;IACnE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+FAA+F;IAC/F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,mBAAmB,GAC7B,IAAI,CAAC,cAAc,EAAE,WAAW,GAAG,WAAW,GAAG,aAAa,CAAC,GAC7D;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,UAAU,EAAE,CAAA;CAAE,CAAC;AAEvD,MAAM,WAAW,kBAAkB;IACjC,oDAAoD;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,2FAA2F;IAC3F,IAAI,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,oDAAoD;IACpD,SAAS,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;IAC7D,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,2FAA2F;AAC3F,MAAM,WAAW,iBAAiB;IAChC,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,CAAC;IACjB,yDAAyD;IACzD,SAAS,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,mBAAmB,EAAE,iBAAgE,CAAC;AAEnG,MAAM,WAAW,YAAY;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,KAAK,EAAE,MAAM,CAAC;IACd;sDACkD;IAClD,MAAM,EAAE,MAAM,CAAC;IACf,oBAAoB,EAAE,OAAO,CAAC;IAC9B,sFAAsF;IACtF,gBAAgB,EAAE,MAAM,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAAC;CACtD;AAED;;+EAE+E;AAC/E,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,WAAW,CAAC,GAAG,MAAM,CAcjF;AASD,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAqB;gBAC3B,IAAI,EAAE,kBAAkB;IAIpC;;;;OAIG;IACH,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,cAAc;IA4C1D,mFAAmF;IACnF,GAAG,IAAI,cAAc,EAAE;IAWvB,+CAA+C;IAC/C,MAAM,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO;IAKpC,+EAA+E;IAC/E,SAAS,CAAC,MAAM,GAAE,iBAAuC,GAAG,YAAY,EAAE;IAgB1E,iFAAiF;IACjF,eAAe,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,GAAE,iBAAuC,GAAG,YAAY;IAKrG,OAAO,CAAC,aAAa;CAwBtB"}
@@ -0,0 +1,191 @@
1
+ /**
2
+ * ApprovalLedger — the durable, signed, append-only record of every operator
3
+ * approval decision, and the per-class agreement ratios computed from it.
4
+ *
5
+ * This is Phase 2 of the approved "Approval-as-Data" spec
6
+ * (`docs/specs/AUTONOMOUS-OPERATION-JUDGMENT-AND-APPROVAL-AS-DATA-SPEC.md`, Part B):
7
+ * an approval stops being a one-shot "approved" with no memory and becomes data —
8
+ * recorded as `approved-as-is` vs `approved-with-change` vs `rejected`, with the
9
+ * WHY of each divergence, so we can (a) see per decision-class where the operator
10
+ * agrees with the agent's recommendation as-is vs revises it, (b) trend toward
11
+ * approved-as-is by folding recurring divergences back into design guidance, and
12
+ * (c) — once a class's ratio holds — pilot auto-approval for that class (Phase 3).
13
+ *
14
+ * SCOPE (operator extension, 2026-06-05): the original spec tracked only official
15
+ * SPEC sign-offs. Justin extended it to track ALL approvals wherever they occur —
16
+ * a spec sign-off, a decision presented and approved in chat, or any other surface.
17
+ * Hence `subject`/`decisionClass`/`surface` (general) rather than `specSlug`/
18
+ * `specClass`, and the spec-only fields (`reviewIterations`/`commitSha`/`evidenceRef`)
19
+ * are optional — a chat approval simply omits them.
20
+ *
21
+ * Mirrors `SessionPoolE2EResultStore` exactly (signed, append-only, tamper-evident,
22
+ * torn-line-tolerant). The store is signer-agnostic — `sign`/`verifySig` are injected
23
+ * — so tests use an Ed25519 keypair and production uses an HMAC over the state secret.
24
+ *
25
+ * AUTHORITY INVARIANT (spec B1, load-bearing for correctness — NOT just integrity):
26
+ * for a MANUAL decision the OPERATOR is the authoritative source of `mode` and
27
+ * `divergences`. The agent must NEVER self-classify the operator's intent. The
28
+ * writer passes the operator-sourced classification (from the approve control / an
29
+ * explicit "as-is" vs "with changes: …"); this store records it faithfully. HMAC/
30
+ * signature guarantees integrity, not correctness — the authoritative-source rule
31
+ * is what prevents an agent inflating its own agreement ratio. An operator can
32
+ * dispute and CORRECT any row: a correction is itself an appended, signed row
33
+ * (`corrects` set), never an in-place edit — history preserved.
34
+ */
35
+ import fs from 'node:fs';
36
+ import path from 'node:path';
37
+ export const APPROVAL_SURFACES = ['spec', 'chat', 'other'];
38
+ export const DIVERGENCE_CATEGORIES = [
39
+ 'missing-principle', 'risk-reduction', 'scope-correction',
40
+ 'efficiency', 'new-information', 'style',
41
+ ];
42
+ export const DEFAULT_ELIGIBILITY = { minRatio: 0.9, minStreak: 5, minTotal: 5 };
43
+ /** Canonical bytes a row's signature covers — field-ordered, signature EXCLUDED.
44
+ * Optional fields are normalized so a present-vs-absent field is unambiguous, and
45
+ * divergences are flattened deterministically so reordering is detectable. */
46
+ export function canonicalApprovalRow(r) {
47
+ return JSON.stringify([
48
+ r.subject,
49
+ r.decisionClass,
50
+ r.surface,
51
+ r.decidedAt,
52
+ r.approver,
53
+ r.mode,
54
+ r.divergences.map((d) => [d.category, d.summary, d.why]),
55
+ r.reviewIterations ?? null,
56
+ r.commitSha ?? '',
57
+ r.evidenceRef ?? '',
58
+ r.corrects ?? '',
59
+ ]);
60
+ }
61
+ function emptyDivergenceCounts() {
62
+ return {
63
+ 'missing-principle': 0, 'risk-reduction': 0, 'scope-correction': 0,
64
+ 'efficiency': 0, 'new-information': 0, 'style': 0,
65
+ };
66
+ }
67
+ export class ApprovalLedger {
68
+ d;
69
+ constructor(deps) {
70
+ this.d = deps;
71
+ }
72
+ /**
73
+ * Append a signed approval row. The `mode` + `divergences` MUST be operator-sourced
74
+ * (the authority invariant above) — this method does not infer them.
75
+ * Throws on an internally-inconsistent row so a malformed record can't pollute ratios.
76
+ */
77
+ recordApproval(input) {
78
+ const divergences = input.divergences ?? [];
79
+ // Consistency guards (correctness, not just integrity).
80
+ if (input.mode === 'approved-as-is' && divergences.length > 0) {
81
+ throw new Error('ApprovalLedger: approved-as-is rows must have no divergences (spec B1).');
82
+ }
83
+ if (input.mode !== 'approved-as-is' && divergences.length === 0) {
84
+ // A change/reject with no stated divergence loses the WHY we exist to capture.
85
+ throw new Error(`ApprovalLedger: mode "${input.mode}" requires at least one divergence with a why.`);
86
+ }
87
+ if (!APPROVAL_SURFACES.includes(input.surface)) {
88
+ throw new Error(`ApprovalLedger: unknown surface "${input.surface}".`);
89
+ }
90
+ for (const dv of divergences) {
91
+ if (!DIVERGENCE_CATEGORIES.includes(dv.category)) {
92
+ throw new Error(`ApprovalLedger: unknown divergence category "${dv.category}".`);
93
+ }
94
+ if (!dv.summary?.trim() || !dv.why?.trim()) {
95
+ throw new Error('ApprovalLedger: each divergence needs a non-empty summary AND why.');
96
+ }
97
+ }
98
+ const decidedAt = input.decidedAt
99
+ ?? new Date(this.d.now ? this.d.now() : Date.now()).toISOString();
100
+ const unsigned = {
101
+ subject: input.subject,
102
+ decisionClass: input.decisionClass,
103
+ surface: input.surface,
104
+ decidedAt,
105
+ approver: input.approver,
106
+ mode: input.mode,
107
+ divergences,
108
+ ...(input.reviewIterations != null ? { reviewIterations: input.reviewIterations } : {}),
109
+ ...(input.commitSha ? { commitSha: input.commitSha } : {}),
110
+ ...(input.evidenceRef ? { evidenceRef: input.evidenceRef } : {}),
111
+ ...(input.corrects ? { corrects: input.corrects } : {}),
112
+ };
113
+ const row = { ...unsigned, signature: this.d.sign(canonicalApprovalRow(unsigned)) };
114
+ fs.mkdirSync(path.dirname(this.d.filePath), { recursive: true });
115
+ fs.appendFileSync(this.d.filePath, JSON.stringify(row) + '\n');
116
+ return row;
117
+ }
118
+ /** All rows (oldest→newest). Tolerant of a missing file / a torn trailing line. */
119
+ all() {
120
+ let content;
121
+ try {
122
+ content = fs.readFileSync(this.d.filePath, 'utf8');
123
+ }
124
+ catch { /* @silent-fallback-ok — ledger file may not exist yet; empty history is the natural default */
125
+ return [];
126
+ }
127
+ const out = [];
128
+ for (const line of content.split('\n')) {
129
+ if (!line.trim())
130
+ continue;
131
+ try {
132
+ out.push(JSON.parse(line));
133
+ }
134
+ catch { /* @silent-fallback-ok — torn-trailing-line tolerance; a crash mid-append must not poison reads */ }
135
+ }
136
+ return out;
137
+ }
138
+ /** Verify a row's signature (tamper check). */
139
+ verify(row) {
140
+ const { signature, ...unsigned } = row;
141
+ return this.d.verifySig(canonicalApprovalRow(unsigned), signature);
142
+ }
143
+ /** Per-class agreement summary. Read-only; never gates by itself (spec B3). */
144
+ summarize(policy = DEFAULT_ELIGIBILITY) {
145
+ const byClass = new Map();
146
+ for (const r of this.all()) {
147
+ const arr = byClass.get(r.decisionClass) ?? [];
148
+ arr.push(r);
149
+ byClass.set(r.decisionClass, arr);
150
+ }
151
+ const summaries = [];
152
+ for (const [decisionClass, rows] of byClass) {
153
+ summaries.push(this.summarizeRows(decisionClass, rows, policy));
154
+ }
155
+ // Stable order: highest-total class first, then alphabetical.
156
+ summaries.sort((a, b) => b.total - a.total || a.decisionClass.localeCompare(b.decisionClass));
157
+ return summaries;
158
+ }
159
+ /** Summary for one class (returns a zeroed summary if the class has no rows). */
160
+ summaryForClass(decisionClass, policy = DEFAULT_ELIGIBILITY) {
161
+ const rows = this.all().filter((r) => r.decisionClass === decisionClass);
162
+ return this.summarizeRows(decisionClass, rows, policy);
163
+ }
164
+ summarizeRows(decisionClass, rows, policy) {
165
+ let approvedAsIs = 0, approvedWithChange = 0, rejected = 0;
166
+ const divergenceCounts = emptyDivergenceCounts();
167
+ for (const r of rows) {
168
+ if (r.mode === 'approved-as-is')
169
+ approvedAsIs++;
170
+ else if (r.mode === 'approved-with-change')
171
+ approvedWithChange++;
172
+ else
173
+ rejected++;
174
+ for (const dv of r.divergences)
175
+ divergenceCounts[dv.category]++;
176
+ }
177
+ const total = rows.length;
178
+ const ratio = total === 0 ? 0 : approvedAsIs / total;
179
+ // CURRENT streak: trailing consecutive approved-as-is from the most-recent row.
180
+ let streak = 0;
181
+ for (let i = rows.length - 1; i >= 0; i--) {
182
+ if (rows[i].mode === 'approved-as-is')
183
+ streak++;
184
+ else
185
+ break;
186
+ }
187
+ const autoApprovalEligible = total >= policy.minTotal && ratio >= policy.minRatio && streak >= policy.minStreak;
188
+ return { decisionClass, total, approvedAsIs, approvedWithChange, rejected, ratio, streak, autoApprovalEligible, divergenceCounts };
189
+ }
190
+ }
191
+ //# sourceMappingURL=ApprovalLedger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ApprovalLedger.js","sourceRoot":"","sources":["../../src/core/ApprovalLedger.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import type { StateManager } from './StateManager.js';
3
+ export declare const DEFAULT_BUILD_CONTEXT_MAX_AGE_MS: number;
4
+ export interface SessionBuildContextEntry {
5
+ spawnCwd: string;
6
+ currentCwd: string;
7
+ branch?: string;
8
+ updatedAt: number;
9
+ }
10
+ export type SessionBuildContextState = Record<string, SessionBuildContextEntry>;
11
+ export interface SessionBuildContextRestore {
12
+ entry: SessionBuildContextEntry;
13
+ note: string;
14
+ }
15
+ export declare function isEligibleBuildContextCwd(spawnCwd: string, currentCwd: string): boolean;
16
+ export declare function formatBuildContextRestoreNote(entry: SessionBuildContextEntry): string;
17
+ export declare class SessionBuildContextStore {
18
+ private readonly state;
19
+ private readonly opts;
20
+ constructor(state: StateManager, opts?: {
21
+ now?: () => number;
22
+ execFileSync?: typeof execFileSync;
23
+ maxAgeMs?: number;
24
+ });
25
+ record(tmuxSession: string, spawnCwd: string, currentCwd: string): void;
26
+ getRestore(tmuxSession: string): SessionBuildContextRestore | null;
27
+ readAll(): SessionBuildContextState;
28
+ private writeAll;
29
+ private readBranch;
30
+ private now;
31
+ private maxAgeMs;
32
+ }
33
+ //# sourceMappingURL=SessionBuildContextStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SessionBuildContextStore.d.ts","sourceRoot":"","sources":["../../src/core/SessionBuildContextStore.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGtD,eAAO,MAAM,gCAAgC,QAAqB,CAAC;AAEnE,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,wBAAwB,GAAG,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC;AAEhF,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,wBAAwB,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAMvF;AAED,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,wBAAwB,GAAG,MAAM,CASrF;AAED,qBAAa,wBAAwB;IAEjC,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,IAAI;gBADJ,KAAK,EAAE,YAAY,EACnB,IAAI,GAAE;QACrB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;QACnB,YAAY,CAAC,EAAE,OAAO,YAAY,CAAC;QACnC,QAAQ,CAAC,EAAE,MAAM,CAAC;KACd;IAGR,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IAqBvE,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,0BAA0B,GAAG,IAAI;IASlE,OAAO,IAAI,wBAAwB;IAsBnC,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,UAAU;IAgBlB,OAAO,CAAC,GAAG;IAIX,OAAO,CAAC,QAAQ;CAGjB"}
@@ -0,0 +1,107 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { execFileSync } from 'node:child_process';
4
+ const STATE_KEY = 'session-build-context';
5
+ export const DEFAULT_BUILD_CONTEXT_MAX_AGE_MS = 6 * 60 * 60 * 1000;
6
+ export function isEligibleBuildContextCwd(spawnCwd, currentCwd) {
7
+ if (!spawnCwd || !currentCwd)
8
+ return false;
9
+ const spawn = path.resolve(spawnCwd);
10
+ const current = path.resolve(currentCwd);
11
+ if (spawn === current)
12
+ return false;
13
+ return current.split(path.sep).includes('.worktrees');
14
+ }
15
+ export function formatBuildContextRestoreNote(entry) {
16
+ const branchLine = entry.branch ? ` branch: ${entry.branch}\n` : '';
17
+ return [
18
+ '[BUILD-CONTEXT RESTORE] Before this restart you were building in:',
19
+ ` worktree: ${entry.currentCwd}`,
20
+ branchLine ? branchLine.trimEnd() : null,
21
+ `Your shell is back in ${entry.spawnCwd} after the restart — cd ${entry.currentCwd} before continuing your build.`,
22
+ 'Do NOT start over in agent-home; your work is in that worktree.',
23
+ ].filter((line) => line != null).join('\n');
24
+ }
25
+ export class SessionBuildContextStore {
26
+ state;
27
+ opts;
28
+ constructor(state, opts = {}) {
29
+ this.state = state;
30
+ this.opts = opts;
31
+ }
32
+ record(tmuxSession, spawnCwd, currentCwd) {
33
+ const now = this.now();
34
+ const all = this.readAll();
35
+ const prior = all[tmuxSession];
36
+ const normalizedSpawn = path.resolve(spawnCwd);
37
+ const normalizedCurrent = path.resolve(currentCwd);
38
+ if (prior?.spawnCwd === normalizedSpawn && prior.currentCwd === normalizedCurrent) {
39
+ return;
40
+ }
41
+ const branch = this.readBranch(normalizedCurrent);
42
+ all[tmuxSession] = {
43
+ spawnCwd: normalizedSpawn,
44
+ currentCwd: normalizedCurrent,
45
+ ...(branch ? { branch } : {}),
46
+ updatedAt: now,
47
+ };
48
+ this.writeAll(all);
49
+ }
50
+ getRestore(tmuxSession) {
51
+ const entry = this.readAll()[tmuxSession];
52
+ if (!entry)
53
+ return null;
54
+ if (!isEligibleBuildContextCwd(entry.spawnCwd, entry.currentCwd))
55
+ return null;
56
+ if (this.now() - entry.updatedAt > this.maxAgeMs())
57
+ return null;
58
+ if (!fs.existsSync(entry.currentCwd))
59
+ return null;
60
+ return { entry, note: formatBuildContextRestoreNote(entry) };
61
+ }
62
+ readAll() {
63
+ const raw = this.state.get(STATE_KEY);
64
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw))
65
+ return {};
66
+ const out = {};
67
+ for (const [key, value] of Object.entries(raw)) {
68
+ if (!value || typeof value !== 'object' || Array.isArray(value))
69
+ continue;
70
+ const entry = value;
71
+ if (typeof entry.spawnCwd !== 'string'
72
+ || typeof entry.currentCwd !== 'string'
73
+ || typeof entry.updatedAt !== 'number')
74
+ continue;
75
+ out[key] = {
76
+ spawnCwd: entry.spawnCwd,
77
+ currentCwd: entry.currentCwd,
78
+ ...(typeof entry.branch === 'string' && entry.branch.trim() ? { branch: entry.branch } : {}),
79
+ updatedAt: entry.updatedAt,
80
+ };
81
+ }
82
+ return out;
83
+ }
84
+ writeAll(state) {
85
+ this.state.set(STATE_KEY, state);
86
+ }
87
+ readBranch(cwd) {
88
+ if (!fs.existsSync(cwd))
89
+ return null;
90
+ try {
91
+ const out = (this.opts.execFileSync ?? execFileSync)('git', ['-C', cwd, 'branch', '--show-current'], { encoding: 'utf-8', timeout: 2000, stdio: ['ignore', 'pipe', 'ignore'] });
92
+ const branch = String(out).trim();
93
+ return branch || null;
94
+ }
95
+ catch {
96
+ // @silent-fallback-ok — branch name is best-effort enrichment; cwd restore still works without it.
97
+ return null;
98
+ }
99
+ }
100
+ now() {
101
+ return this.opts.now?.() ?? Date.now();
102
+ }
103
+ maxAgeMs() {
104
+ return this.opts.maxAgeMs ?? DEFAULT_BUILD_CONTEXT_MAX_AGE_MS;
105
+ }
106
+ }
107
+ //# sourceMappingURL=SessionBuildContextStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SessionBuildContextStore.js","sourceRoot":"","sources":["../../src/core/SessionBuildContextStore.ts"],"names":[],"mappings":"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"}
@@ -163,6 +163,7 @@ export declare class SessionManager extends EventEmitter {
163
163
  private _cachedRunningSessions;
164
164
  /** Worktree manager — when set, spawnSession resolves an isolated worktree per topic. */
165
165
  private worktreeManager;
166
+ private buildContextStore;
166
167
  /** Per-session shim directory root (one subdir per session). Used for K9 mandatory shim. */
167
168
  private shimRoot;
168
169
  constructor(config: SessionManagerConfig, state: StateManager);
@@ -533,6 +534,9 @@ export declare class SessionManager extends EventEmitter {
533
534
  * Capture the current output of a tmux session.
534
535
  */
535
536
  captureOutput(tmuxSession: string, lines?: number): string | null;
537
+ private currentPaneCwd;
538
+ private recordBuildContext;
539
+ private withBuildContextRestoreNote;
536
540
  /**
537
541
  * Resolve the IntelligenceFramework a tmux session was spawned under
538
542
  * by reading the INSTAR_FRAMEWORK env we set in its tmux env block.