@eir-labs/coltrane 0.8.0 → 0.8.1

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 (77) hide show
  1. package/agents/change-verifier.json +40 -0
  2. package/agents/code-implementer.json +42 -0
  3. package/agents/contract-definer.json +31 -0
  4. package/agents/defect-investigator.json +60 -0
  5. package/agents/grounder.json +38 -0
  6. package/agents/pr-publisher.json +48 -0
  7. package/agents/red-spec-drafter.json +46 -0
  8. package/agents/spec-publisher.json +48 -0
  9. package/agents/spec-reviewer.json +32 -0
  10. package/dist/src/bifrost_invoker.js +53 -52
  11. package/dist/src/bifrost_invoker.js.map +1 -1
  12. package/dist/src/change_set_branch.d.ts +131 -0
  13. package/dist/src/change_set_branch.js +179 -0
  14. package/dist/src/change_set_branch.js.map +1 -0
  15. package/dist/src/claude_invoker.d.ts +79 -2
  16. package/dist/src/claude_invoker.js +377 -36
  17. package/dist/src/claude_invoker.js.map +1 -1
  18. package/dist/src/cli.d.ts +1 -1
  19. package/dist/src/cli.js +26 -3
  20. package/dist/src/cli.js.map +1 -1
  21. package/dist/src/composition.d.ts +11 -0
  22. package/dist/src/composition.js +3 -1
  23. package/dist/src/composition.js.map +1 -1
  24. package/dist/src/genome_schema.d.ts +301 -10
  25. package/dist/src/genome_schema.js +228 -7
  26. package/dist/src/genome_schema.js.map +1 -1
  27. package/dist/src/genome_store.d.ts +16 -1
  28. package/dist/src/genome_store.js +68 -0
  29. package/dist/src/genome_store.js.map +1 -1
  30. package/dist/src/gig_tracker.js +4 -0
  31. package/dist/src/gig_tracker.js.map +1 -1
  32. package/dist/src/hosted_tools.js +19 -0
  33. package/dist/src/hosted_tools.js.map +1 -1
  34. package/dist/src/institution_enforcement.d.ts +68 -0
  35. package/dist/src/institution_enforcement.js +417 -0
  36. package/dist/src/institution_enforcement.js.map +1 -0
  37. package/dist/src/institution_loader.d.ts +43 -0
  38. package/dist/src/institution_loader.js +157 -0
  39. package/dist/src/institution_loader.js.map +1 -0
  40. package/dist/src/loader.d.ts +3 -1
  41. package/dist/src/loader.js +12 -1
  42. package/dist/src/loader.js.map +1 -1
  43. package/dist/src/mcp.js +8 -1
  44. package/dist/src/mcp.js.map +1 -1
  45. package/dist/src/runtime.d.ts +66 -0
  46. package/dist/src/runtime.js +202 -2
  47. package/dist/src/runtime.js.map +1 -1
  48. package/dist/src/server.d.ts +7 -0
  49. package/dist/src/server.js +90 -1
  50. package/dist/src/server.js.map +1 -1
  51. package/dist/src/venue_realize.d.ts +90 -0
  52. package/dist/src/venue_realize.js +129 -0
  53. package/dist/src/venue_realize.js.map +1 -0
  54. package/dist/src/version.d.ts +1 -1
  55. package/dist/src/version.js +1 -1
  56. package/dist/src/worker.d.ts +45 -3
  57. package/dist/src/worker.js +67 -2
  58. package/dist/src/worker.js.map +1 -1
  59. package/domain_types/change-request.json +2 -1
  60. package/domain_types/class-sweep.json +34 -0
  61. package/domain_types/defect-class.json +25 -0
  62. package/domain_types/defect-location.json +28 -0
  63. package/domain_types/defect-report.json +28 -0
  64. package/domain_types/fix-spec.json +28 -0
  65. package/domain_types/fix-verification.json +28 -0
  66. package/domain_types/grounding-dossier.json +95 -0
  67. package/domain_types/pull-request.json +43 -0
  68. package/domain_types/red-spec.json +76 -0
  69. package/domain_types/reproduction.json +28 -0
  70. package/domain_types/root-cause.json +28 -0
  71. package/domain_types/subsystem-contract.json +106 -0
  72. package/institutions/coltrane.json +166 -0
  73. package/institutions/quartet.json +143 -26
  74. package/package.json +2 -1
  75. package/standards/defect-investigation-v1.json +156 -0
  76. package/standards/software-change-pr-v1.json +151 -0
  77. package/standards/spec-drafting-v1.json +125 -0
@@ -0,0 +1,131 @@
1
+ /** The institution's protected main line — the branch Law C gates, and a change-set branch is NOT. */
2
+ export declare const PROTECTED_MAIN_LINE = "main";
3
+ /** The namespace prefix that marks a change-set branch. */
4
+ export declare const CHANGE_SET_BRANCH_PREFIX = "changeset/";
5
+ /** The standard the trigger dispatches when a RED spec PR is merged into the change-set branch. */
6
+ export declare const IMPLEMENTATION_STANDARD = "software-change-pr-v1";
7
+ /**
8
+ * Derive the change-set branch name from the ORIGINATING (spec) gig id — never the current gig id.
9
+ * Format: `changeset/<full-originating-gig-uuid>[/<slug>]`. The full UUID is the SOLE key
10
+ * (truncation would forfeit the collision-freedom the branch exists to guarantee); the slug is
11
+ * decorative, rides alongside for humans, and is OUTSIDE the key.
12
+ */
13
+ export declare function deriveChangeSetBranch(originatingGigId: string, slug?: string): string;
14
+ /** Parse the ORIGINATING gig id back out of a change-set branch name (the slug is ignored). */
15
+ export declare function parseOriginatingGig(branchName: string): string;
16
+ /** Whether a name is a well-formed change-set branch (prefix + a parseable originating gig id). */
17
+ export declare function isChangeSetBranch(branchName: string): boolean;
18
+ /** The base branch the RED spec PR targets: the change-set branch derived from the originating gig. */
19
+ export declare function specPrBase(originatingGigId: string): string;
20
+ /** The base branch the GREEN implementation PR targets: the SAME change-set branch it branched from. */
21
+ export declare function implPrBase(changeSetBranch: string): string;
22
+ export type CreateOutcome = {
23
+ created: true;
24
+ reused: false;
25
+ branch: string;
26
+ } | {
27
+ created: false;
28
+ reused: true;
29
+ branch: string;
30
+ };
31
+ /**
32
+ * Create-if-absent / reuse-if-present. A resumed gig that re-publishes MUST reuse the existing
33
+ * change-set branch, never fork a second one and never clobber/force-reset the first.
34
+ */
35
+ export declare function ensureChangeSetBranch(branch: string, existingBranches: readonly string[]): CreateOutcome;
36
+ export type PublishGate = {
37
+ ok: true;
38
+ } | {
39
+ ok: false;
40
+ refusal: "dead-branch";
41
+ base: string;
42
+ };
43
+ /**
44
+ * Pre-flight, at the same altitude as runtime.ts's missing-gig-input hard stop: a publish that
45
+ * would seal a pull-request whose base branch does not exist on the remote is refused as a DEAD
46
+ * NAME — nothing is sealed. Same defect class the engine already refuses for dead tool-grants.
47
+ */
48
+ export declare function assertBasePublishable(base: string, remoteBranches: readonly string[]): PublishGate;
49
+ export interface ChangeRequestBranchCarrier {
50
+ /** The change-set branch the implementation run must work on. Additive optional field. */
51
+ change_set_branch?: string;
52
+ }
53
+ export type BranchResolution = {
54
+ ok: true;
55
+ branch: string;
56
+ } | {
57
+ ok: false;
58
+ refusal: "branch-absent-but-expected";
59
+ };
60
+ /**
61
+ * Resolve the branch the implementation run works on. It is CARRIED in the change-request; the
62
+ * working-tree branch is NOT consulted (inference is how a run lands on the wrong branch
63
+ * silently). Absent-but-expected is a hard stop, not a guess.
64
+ */
65
+ export declare function resolveImplementationBranch(req: ChangeRequestBranchCarrier, workingTreeBranch: string): BranchResolution;
66
+ export interface RedSpecMergedEvent {
67
+ /** The change-set branch the RED spec PR was merged into. The idempotency key. */
68
+ change_set_branch: string;
69
+ /** The originating (spec) gig id the branch is keyed by. */
70
+ originating_gig_id: string;
71
+ /** GitHub's X-GitHub-Delivery id — at-least-once delivery, so this only guards literal re-sends. */
72
+ delivery_id: string;
73
+ }
74
+ export interface EnqueuedImplementation {
75
+ standard: string;
76
+ change_request: ChangeRequestBranchCarrier & Record<string, unknown>;
77
+ }
78
+ /**
79
+ * The trigger consumer. Its ONLY job is at-most-once ENQUEUE keyed on the change-set branch: the
80
+ * queue's atomic claim/lease already gives at-most-once RUN. Firing twice (a duplicate
81
+ * X-GitHub-Delivery, or a different delivery of the same logical event) must enqueue nothing the
82
+ * second time — the branch already carries an in-flight implementation gig.
83
+ */
84
+ export declare class ChangeSetTrigger {
85
+ /** Enqueued implementations keyed on the change-set branch — the idempotency key, not the delivery id. */
86
+ private readonly byBranch;
87
+ /** Handle one 'RED spec PR merged into the change-set branch' event. */
88
+ handle(event: RedSpecMergedEvent): void;
89
+ /** The implementation gigs enqueued for a change-set branch — 0 or exactly 1. */
90
+ enqueued(changeSetBranch: string): readonly EnqueuedImplementation[];
91
+ }
92
+ export type ChangeSetState = "none" | "red" | "green" | "retired";
93
+ export type LifecycleCommand = {
94
+ kind: "create";
95
+ } | {
96
+ kind: "merge-red";
97
+ } | {
98
+ kind: "merge-green";
99
+ } | {
100
+ kind: "promote-to-main";
101
+ } | {
102
+ kind: "retire";
103
+ by: string;
104
+ };
105
+ /** A merge the machine performed while applying a command — action + the branch it targeted. */
106
+ export interface MergeAttempt {
107
+ action: "merge-red" | "merge-green" | "promote-to-main";
108
+ target_branch: string;
109
+ }
110
+ export interface RetirementRecord {
111
+ branch: string;
112
+ by: string;
113
+ }
114
+ /**
115
+ * The change-set branch as a state machine. The load-bearing invariants: a red merge (the spec or
116
+ * the implementation PR) is NEVER performed against the protected main line (Law C composition),
117
+ * the branch is retired at most once, and every retirement is RECORDED, never silent.
118
+ */
119
+ export declare class ChangeSetBranchMachine {
120
+ private readonly changeSetBranch;
121
+ private _state;
122
+ private readonly _retirements;
123
+ constructor(changeSetBranch: string);
124
+ state(): ChangeSetState;
125
+ /** Apply a command; return the merge attempts it performed (so a test can assert their targets). */
126
+ apply(cmd: LifecycleCommand): readonly MergeAttempt[];
127
+ /** How many times this branch has been retired (must never exceed 1). */
128
+ retiredCount(): number;
129
+ /** The recorded retirement events — length must equal retiredCount(); a silent retire is refused. */
130
+ retirementLog(): readonly RetirementRecord[];
131
+ }
@@ -0,0 +1,179 @@
1
+ // SEAM — the change-set branch subsystem, authored as explicitly-stubbed THROWING signatures.
2
+ //
3
+ // This file exists so the RED spec COMPILES (`tsc` / `npm run build` green) while every
4
+ // assertion in tests/change_set/** reds because the ENFORCEMENT is absent — never because a
5
+ // symbol is missing or a type does not line up. It is the same discipline
6
+ // src/institution_enforcement.ts uses on branch spec/coltrane-enforces-its-laws and
7
+ // src/venue_realize.ts uses on the venue-realization branches: real signatures, real return
8
+ // types, bodies that throw. No behaviour is implemented here. The implementation pipeline
9
+ // (software-change-pr-v1, running off the change-set branch) is what turns these red tests green.
10
+ //
11
+ // The subsystem: a change set is one identity that outlives every commit and spans TWO gigs —
12
+ // the spec-drafting gig that opens the RED spec, and the DIFFERENT implementation gig that turns
13
+ // it green. It can therefore be keyed only by the ORIGINATING (spec) gig. Prior art:
14
+ // - Gerrit Change-Id — a stable id above the commit, associating many patch sets with one
15
+ // logical change; its per-target-branch scoping is exactly why a 1:1 branch↔gig assumption
16
+ // breaks (git-review Documentation/user-changeid.html).
17
+ // - Graphite stacked diffs — a PR whose base is another branch (not main) is a normal,
18
+ // mergeable workflow, so the RED spec PR and the GREEN implementation PR can both target the
19
+ // change-set branch and main never sees red.
20
+ // - git-check-ref-format(1) — a UUID and a hyphen/underscore slug are both legal ref
21
+ // components, so `changeset/<uuid>[/<slug>]` is a well-formed ref.
22
+ const STUB = "change_set_branch: enforcement not implemented (RED spec seam)";
23
+ /** The institution's protected main line — the branch Law C gates, and a change-set branch is NOT. */
24
+ export const PROTECTED_MAIN_LINE = "main";
25
+ /** The namespace prefix that marks a change-set branch. */
26
+ export const CHANGE_SET_BRANCH_PREFIX = "changeset/";
27
+ /** The standard the trigger dispatches when a RED spec PR is merged into the change-set branch. */
28
+ export const IMPLEMENTATION_STANDARD = "software-change-pr-v1";
29
+ // ── Branch identity: derived from the ORIGINATING gig, round-trippable, injective ───────────────
30
+ /**
31
+ * Derive the change-set branch name from the ORIGINATING (spec) gig id — never the current gig id.
32
+ * Format: `changeset/<full-originating-gig-uuid>[/<slug>]`. The full UUID is the SOLE key
33
+ * (truncation would forfeit the collision-freedom the branch exists to guarantee); the slug is
34
+ * decorative, rides alongside for humans, and is OUTSIDE the key.
35
+ */
36
+ export function deriveChangeSetBranch(originatingGigId, slug) {
37
+ // The full gig id is the SOLE key segment; the slug, when present, rides in a SEPARATE ref
38
+ // component after it, so parsing (which reads only the first segment after the prefix) ignores it.
39
+ const key = `${CHANGE_SET_BRANCH_PREFIX}${originatingGigId}`;
40
+ return slug ? `${key}/${slug}` : key;
41
+ }
42
+ /** Parse the ORIGINATING gig id back out of a change-set branch name (the slug is ignored). */
43
+ export function parseOriginatingGig(branchName) {
44
+ // Strip the namespace, then take the FIRST path component: that is the key. A trailing slug is a
45
+ // later component and is dropped. A gig UUID carries no "/", so it survives the split whole.
46
+ const rest = branchName.slice(CHANGE_SET_BRANCH_PREFIX.length);
47
+ return rest.split("/")[0];
48
+ }
49
+ /** Whether a name is a well-formed change-set branch (prefix + a parseable originating gig id). */
50
+ export function isChangeSetBranch(branchName) {
51
+ if (!branchName.startsWith(CHANGE_SET_BRANCH_PREFIX))
52
+ return false;
53
+ return parseOriginatingGig(branchName).length > 0;
54
+ }
55
+ // ── Publish targets: both PRs target the change-set branch, never the protected main line ───────
56
+ /** The base branch the RED spec PR targets: the change-set branch derived from the originating gig. */
57
+ export function specPrBase(originatingGigId) {
58
+ return deriveChangeSetBranch(originatingGigId);
59
+ }
60
+ /** The base branch the GREEN implementation PR targets: the SAME change-set branch it branched from. */
61
+ export function implPrBase(changeSetBranch) {
62
+ // The implementation branches FROM and targets the SAME change-set branch — an identity, not a
63
+ // re-derivation, so `implPrBase(specPrBase(g)) === specPrBase(g)` holds structurally.
64
+ return changeSetBranch;
65
+ }
66
+ /**
67
+ * Create-if-absent / reuse-if-present. A resumed gig that re-publishes MUST reuse the existing
68
+ * change-set branch, never fork a second one and never clobber/force-reset the first.
69
+ */
70
+ export function ensureChangeSetBranch(branch, existingBranches) {
71
+ // Present ⇒ reuse (never fork a second, never clobber/force-reset the first); absent ⇒ create.
72
+ return existingBranches.includes(branch)
73
+ ? { created: false, reused: true, branch }
74
+ : { created: true, reused: false, branch };
75
+ }
76
+ /**
77
+ * Pre-flight, at the same altitude as runtime.ts's missing-gig-input hard stop: a publish that
78
+ * would seal a pull-request whose base branch does not exist on the remote is refused as a DEAD
79
+ * NAME — nothing is sealed. Same defect class the engine already refuses for dead tool-grants.
80
+ */
81
+ export function assertBasePublishable(base, remoteBranches) {
82
+ // A base absent from the remote is a DEAD NAME — refused before any PR is sealed, the same
83
+ // fail-closed altitude as a dead tool-grant. Present ⇒ the publish may proceed.
84
+ return remoteBranches.includes(base) ? { ok: true } : { ok: false, refusal: "dead-branch", base };
85
+ }
86
+ /**
87
+ * Resolve the branch the implementation run works on. It is CARRIED in the change-request; the
88
+ * working-tree branch is NOT consulted (inference is how a run lands on the wrong branch
89
+ * silently). Absent-but-expected is a hard stop, not a guess.
90
+ */
91
+ export function resolveImplementationBranch(req, workingTreeBranch) {
92
+ // The branch is CARRIED in the change-request. The working tree is NEVER consulted (inference is
93
+ // how a run lands on the wrong branch silently), so `workingTreeBranch` is deliberately unused.
94
+ void workingTreeBranch;
95
+ if (req.change_set_branch === undefined || req.change_set_branch.length === 0) {
96
+ return { ok: false, refusal: "branch-absent-but-expected" };
97
+ }
98
+ return { ok: true, branch: req.change_set_branch };
99
+ }
100
+ /**
101
+ * The trigger consumer. Its ONLY job is at-most-once ENQUEUE keyed on the change-set branch: the
102
+ * queue's atomic claim/lease already gives at-most-once RUN. Firing twice (a duplicate
103
+ * X-GitHub-Delivery, or a different delivery of the same logical event) must enqueue nothing the
104
+ * second time — the branch already carries an in-flight implementation gig.
105
+ */
106
+ export class ChangeSetTrigger {
107
+ /** Enqueued implementations keyed on the change-set branch — the idempotency key, not the delivery id. */
108
+ byBranch = new Map();
109
+ /** Handle one 'RED spec PR merged into the change-set branch' event. */
110
+ handle(event) {
111
+ // At-most-once ENQUEUE keyed on the branch: once a branch carries an in-flight implementation,
112
+ // every later delivery — a duplicate id or a fresh delivery of the same logical event — is a
113
+ // no-op. The queue's atomic claim/lease is what gives at-most-once RUN; this guards the enqueue.
114
+ if (this.byBranch.has(event.change_set_branch))
115
+ return;
116
+ this.byBranch.set(event.change_set_branch, {
117
+ standard: IMPLEMENTATION_STANDARD,
118
+ change_request: { change_set_branch: event.change_set_branch },
119
+ });
120
+ }
121
+ /** The implementation gigs enqueued for a change-set branch — 0 or exactly 1. */
122
+ enqueued(changeSetBranch) {
123
+ const one = this.byBranch.get(changeSetBranch);
124
+ return one ? [one] : [];
125
+ }
126
+ }
127
+ /**
128
+ * The change-set branch as a state machine. The load-bearing invariants: a red merge (the spec or
129
+ * the implementation PR) is NEVER performed against the protected main line (Law C composition),
130
+ * the branch is retired at most once, and every retirement is RECORDED, never silent.
131
+ */
132
+ export class ChangeSetBranchMachine {
133
+ changeSetBranch;
134
+ _state = "none";
135
+ _retirements = [];
136
+ constructor(changeSetBranch) {
137
+ this.changeSetBranch = changeSetBranch;
138
+ void this.changeSetBranch;
139
+ }
140
+ state() {
141
+ return this._state;
142
+ }
143
+ /** Apply a command; return the merge attempts it performed (so a test can assert their targets). */
144
+ apply(cmd) {
145
+ switch (cmd.kind) {
146
+ case "create":
147
+ // Creating the branch is not a merge — it produces no MergeAttempt.
148
+ return [];
149
+ case "merge-red":
150
+ // The RED spec PR merges INTO the change-set branch — NEVER the protected main line.
151
+ this._state = "red";
152
+ return [{ action: "merge-red", target_branch: this.changeSetBranch }];
153
+ case "merge-green":
154
+ // The GREEN implementation PR merges INTO the change-set branch — again never main.
155
+ this._state = "green";
156
+ return [{ action: "merge-green", target_branch: this.changeSetBranch }];
157
+ case "promote-to-main":
158
+ // The ONLY main-targeting merge: the final change-set→main PR, merged by the governor on
159
+ // green CI. It is not a red merge, so it composes with Law C rather than contradicting it.
160
+ return [{ action: "promote-to-main", target_branch: PROTECTED_MAIN_LINE }];
161
+ case "retire":
162
+ // At-most-once, and always RECORDED — a second retire is a no-op, never a silent delete.
163
+ if (this._retirements.length === 0) {
164
+ this._retirements.push({ branch: this.changeSetBranch, by: cmd.by });
165
+ this._state = "retired";
166
+ }
167
+ return [];
168
+ }
169
+ }
170
+ /** How many times this branch has been retired (must never exceed 1). */
171
+ retiredCount() {
172
+ return this._retirements.length;
173
+ }
174
+ /** The recorded retirement events — length must equal retiredCount(); a silent retire is refused. */
175
+ retirementLog() {
176
+ return this._retirements;
177
+ }
178
+ }
179
+ //# sourceMappingURL=change_set_branch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"change_set_branch.js","sourceRoot":"","sources":["../../src/change_set_branch.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,EAAE;AACF,wFAAwF;AACxF,4FAA4F;AAC5F,0EAA0E;AAC1E,oFAAoF;AACpF,4FAA4F;AAC5F,0FAA0F;AAC1F,kGAAkG;AAClG,EAAE;AACF,8FAA8F;AAC9F,iGAAiG;AACjG,qFAAqF;AACrF,4FAA4F;AAC5F,+FAA+F;AAC/F,4DAA4D;AAC5D,yFAAyF;AACzF,iGAAiG;AACjG,iDAAiD;AACjD,uFAAuF;AACvF,uEAAuE;AAEvE,MAAM,IAAI,GAAG,gEAAgE,CAAC;AAE9E,sGAAsG;AACtG,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,CAAC;AAE1C,2DAA2D;AAC3D,MAAM,CAAC,MAAM,wBAAwB,GAAG,YAAY,CAAC;AAErD,mGAAmG;AACnG,MAAM,CAAC,MAAM,uBAAuB,GAAG,uBAAuB,CAAC;AAE/D,mGAAmG;AAEnG;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,gBAAwB,EAAE,IAAa;IAC3E,2FAA2F;IAC3F,mGAAmG;IACnG,MAAM,GAAG,GAAG,GAAG,wBAAwB,GAAG,gBAAgB,EAAE,CAAC;IAC7D,OAAO,IAAI,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;AACvC,CAAC;AAED,+FAA+F;AAC/F,MAAM,UAAU,mBAAmB,CAAC,UAAkB;IACpD,iGAAiG;IACjG,6FAA6F;IAC7F,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,wBAAwB,CAAC,MAAM,CAAC,CAAC;IAC/D,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC;AAC7B,CAAC;AAED,mGAAmG;AACnG,MAAM,UAAU,iBAAiB,CAAC,UAAkB;IAClD,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,wBAAwB,CAAC;QAAE,OAAO,KAAK,CAAC;IACnE,OAAO,mBAAmB,CAAC,UAAU,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;AACpD,CAAC;AAED,mGAAmG;AAEnG,uGAAuG;AACvG,MAAM,UAAU,UAAU,CAAC,gBAAwB;IACjD,OAAO,qBAAqB,CAAC,gBAAgB,CAAC,CAAC;AACjD,CAAC;AAED,wGAAwG;AACxG,MAAM,UAAU,UAAU,CAAC,eAAuB;IAChD,+FAA+F;IAC/F,sFAAsF;IACtF,OAAO,eAAe,CAAC;AACzB,CAAC;AAQD;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CACnC,MAAc,EACd,gBAAmC;IAEnC,+FAA+F;IAC/F,OAAO,gBAAgB,CAAC,QAAQ,CAAC,MAAM,CAAC;QACtC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE;QAC1C,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;AAC/C,CAAC;AAQD;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CACnC,IAAY,EACZ,cAAiC;IAEjC,2FAA2F;IAC3F,gFAAgF;IAChF,OAAO,cAAc,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;AACpG,CAAC;AAaD;;;;GAIG;AACH,MAAM,UAAU,2BAA2B,CACzC,GAA+B,EAC/B,iBAAyB;IAEzB,iGAAiG;IACjG,gGAAgG;IAChG,KAAK,iBAAiB,CAAC;IACvB,IAAI,GAAG,CAAC,iBAAiB,KAAK,SAAS,IAAI,GAAG,CAAC,iBAAiB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9E,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,4BAA4B,EAAE,CAAC;IAC9D,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,iBAAiB,EAAE,CAAC;AACrD,CAAC;AAkBD;;;;;GAKG;AACH,MAAM,OAAO,gBAAgB;IAC3B,0GAA0G;IACzF,QAAQ,GAAG,IAAI,GAAG,EAAkC,CAAC;IAEtE,wEAAwE;IACxE,MAAM,CAAC,KAAyB;QAC9B,+FAA+F;QAC/F,6FAA6F;QAC7F,iGAAiG;QACjG,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,iBAAiB,CAAC;YAAE,OAAO;QACvD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,iBAAiB,EAAE;YACzC,QAAQ,EAAE,uBAAuB;YACjC,cAAc,EAAE,EAAE,iBAAiB,EAAE,KAAK,CAAC,iBAAiB,EAAE;SAC/D,CAAC,CAAC;IACL,CAAC;IAED,iFAAiF;IACjF,QAAQ,CAAC,eAAuB;QAC9B,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;QAC/C,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1B,CAAC;CACF;AAwBD;;;;GAIG;AACH,MAAM,OAAO,sBAAsB;IAIJ;IAHrB,MAAM,GAAmB,MAAM,CAAC;IACvB,YAAY,GAAuB,EAAE,CAAC;IAEvD,YAA6B,eAAuB;QAAvB,oBAAe,GAAf,eAAe,CAAQ;QAClD,KAAK,IAAI,CAAC,eAAe,CAAC;IAC5B,CAAC;IAED,KAAK;QACH,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED,oGAAoG;IACpG,KAAK,CAAC,GAAqB;QACzB,QAAQ,GAAG,CAAC,IAAI,EAAE,CAAC;YACjB,KAAK,QAAQ;gBACX,oEAAoE;gBACpE,OAAO,EAAE,CAAC;YACZ,KAAK,WAAW;gBACd,qFAAqF;gBACrF,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;gBACpB,OAAO,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,aAAa,EAAE,IAAI,CAAC,eAAe,EAAE,CAAC,CAAC;YACxE,KAAK,aAAa;gBAChB,oFAAoF;gBACpF,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC;gBACtB,OAAO,CAAC,EAAE,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,IAAI,CAAC,eAAe,EAAE,CAAC,CAAC;YAC1E,KAAK,iBAAiB;gBACpB,yFAAyF;gBACzF,2FAA2F;gBAC3F,OAAO,CAAC,EAAE,MAAM,EAAE,iBAAiB,EAAE,aAAa,EAAE,mBAAmB,EAAE,CAAC,CAAC;YAC7E,KAAK,QAAQ;gBACX,yFAAyF;gBACzF,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBACnC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,eAAe,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;oBACrE,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC;gBAC1B,CAAC;gBACD,OAAO,EAAE,CAAC;QACd,CAAC;IACH,CAAC;IAED,yEAAyE;IACzE,YAAY;QACV,OAAO,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC;IAClC,CAAC;IAED,qGAAqG;IACrG,aAAa;QACX,OAAO,IAAI,CAAC,YAAY,CAAC;IAC3B,CAAC;CACF"}
@@ -24,7 +24,22 @@ export declare const DEPTH_MAX_TOOL_CALLS: Partial<Record<Depth, number>>;
24
24
  * ever needs to differ from the seal's again, that difference must be argued here.
25
25
  */
26
26
  export declare function promptSchemaFor(registry: Registry | undefined, slug: string | undefined): Record<string, unknown> | undefined;
27
- export declare function buildPrompt(ctx: AgentInvocationContext, outputSchema?: Record<string, unknown>, outputSchemas?: Record<string, Record<string, unknown> | undefined>): string;
27
+ /**
28
+ * When set, the Task layer tells the chair to seal each output IN-BAND by calling `output_write`
29
+ * (the write-boundary tool) rather than printing final-text JSON for the invoker to parse. The
30
+ * tool runs the full seal predicate and returns its verdict in-band, so the agent self-corrects
31
+ * within its own single run — there is no invoker re-prompt. Absent → the legacy text-seal Task
32
+ * (unchanged, so every buildPrompt fixture stays stable).
33
+ */
34
+ export interface OutputWriteSeal {
35
+ via: "output_write";
36
+ gig_id: string;
37
+ agent_slug: string;
38
+ phase: string;
39
+ /** domain_type slug → its core_type, so the agent passes the right `core_type` to output_write. */
40
+ core_by_type: Record<string, string>;
41
+ }
42
+ export declare function buildPrompt(ctx: AgentInvocationContext, outputSchema?: Record<string, unknown>, outputSchemas?: Record<string, Record<string, unknown> | undefined>, seal?: OutputWriteSeal): string;
28
43
  /**
29
44
  * A typed extraction failure. Carries the number of balanced JSON objects found and a
30
45
  * bounded excerpt of the raw text — previously both throws were bare `Error`s with no
@@ -37,6 +52,23 @@ export declare class ModelOutputParseError extends Error {
37
52
  readonly excerpt: string;
38
53
  constructor(message: string, candidateCount: number, raw: string);
39
54
  }
55
+ /**
56
+ * A chair completed its run but sealed NONE of its promised outputs through `output_write`.
57
+ * Distinct from ModelOutputParseError (a text-seal chair that emitted no parseable answer): this
58
+ * is the output_write-seal path, where the boundary that adjudicates a payload against its full
59
+ * output contract is the chair's own in-band `output_write` call (validated by the engine's
60
+ * checkWritable, corrected in-band by the agent), NOT a re-prompt from this invoker. A chair that
61
+ * never gets a single write past that boundary produced nothing this invoker can hand back.
62
+ *
63
+ * The runtime's own floor check (executeChair, `missingRequired`) also catches a shortfall and is
64
+ * the authority on which promised types were merely optional; this error is the earlier, chair-
65
+ * local signal that the write boundary sealed nothing at all.
66
+ */
67
+ export declare class ModelOutputContractError extends Error {
68
+ readonly slug: string;
69
+ readonly reason: string;
70
+ constructor(slug: string, reason: string);
71
+ }
40
72
  export interface ExtractJsonOptions {
41
73
  /**
42
74
  * Keys the answer is expected to carry. A candidate matches only if it contains **all**
@@ -93,6 +125,21 @@ export declare function extractJson(text: string, opts?: ExtractJsonOptions): Re
93
125
  * no signal, so refuse to guess between rival candidates.
94
126
  */
95
127
  export declare function extractOptionsForChair(sealTypes: readonly string[], schema: Record<string, unknown> | undefined): ExtractJsonOptions;
128
+ /**
129
+ * Extract the chair's sealed payloads from a stream-json stdout, keyed by the chair's seal types.
130
+ * Returns the blob shape the runtime already consumes (a key per domain_type, or the bare data for
131
+ * a lone single-output write), so executeChair seals it through its one boundary unchanged.
132
+ */
133
+ /** A child that exited non-zero, carrying the stdout it produced before dying.
134
+ *
135
+ * The stream is not incidental to the error — for a chair stopped by its tool budget it holds
136
+ * validated payloads, and discarding it destroys work the engine had already adjudicated as good.
137
+ * Exported because the injected-run seam (`opts.run`) is how tests reproduce a non-zero exit. */
138
+ export declare class ChildExitError extends Error {
139
+ readonly stdout: string;
140
+ constructor(message: string, stdout: string);
141
+ }
142
+ export declare function captureOutputWrites(stdout: string, sealTypes: readonly string[]): Record<string, unknown>;
96
143
  export declare const DEFAULT_CHAIR_TIMEOUT_MS: number;
97
144
  export declare const DEFAULT_ABORT_GRACE_MS = 2000;
98
145
  /** How many chair children are alive right now (observability for the shutdown path). */
@@ -107,14 +154,44 @@ export interface SpawnBounds {
107
154
  export interface ClaudeInvokerOptions {
108
155
  bin?: string | undefined;
109
156
  model?: string | undefined;
157
+ /**
158
+ * Turns granted ONCE, as a reserve, to a chair that exhausted its declared turn budget.
159
+ *
160
+ * `--max-turns` is a hard CLI bound with no callback, so without this a chair learns its budget
161
+ * only by dying at it — cut off mid-reach, before it can write the boundary record that says what
162
+ * it did NOT get to. The reserve turns that silent truncation into a stated one: the chair is told
163
+ * it is in reserve, how many turns remain, and what it already sealed, and is asked to close out.
164
+ *
165
+ * It cannot be delivered in-band. A chair's tools are typically HOST tools (WebSearch, WebFetch)
166
+ * that the child's own coltrane server never sees, so the engine surface cannot count turns. The
167
+ * parent sees every turn in the stream, and its only channel into a running child is a new
168
+ * invocation — hence a continuation rather than a signal.
169
+ *
170
+ * ONE extension, never a loop: an unbounded "just a bit more" is not a budget. Unset = the prior
171
+ * behaviour exactly (keep whatever passed the write boundary, grant nothing).
172
+ */
173
+ turn_reserve?: number | undefined;
110
174
  registry?: Registry | undefined;
111
175
  mcpServers?: Record<string, unknown> | undefined;
112
176
  toolProviders?: ToolProviderRegistry | undefined;
113
177
  mcpServerConfigs?: Record<string, unknown> | undefined;
114
- run?: ((bin: string, args: string[], spawn: SpawnBounds) => string | Promise<string>) | undefined;
178
+ run?: ((bin: string, args: string[], spawn: SpawnBounds, env?: Record<string, string>) => string | Promise<string>) | undefined;
115
179
  timeout_ms?: number | undefined;
116
180
  abort_grace_ms?: number | undefined;
117
181
  parent_session_id?: string | undefined;
182
+ /**
183
+ * How a model chair produces its sealed output.
184
+ * - "output_write" (production): the chair SEALS IN-BAND by calling `output_write` during its
185
+ * run. The spawn advertises `mcp__coltrane__output_write` and its coltrane server runs in
186
+ * validate-mode (COLTRANE_OUTPUT_WRITE_MODE=validate), so each call adjudicates the payload
187
+ * against the FULL seal predicate and returns the verdict in-band — the agent self-corrects
188
+ * within its single run. The invoker captures the validated payload from the chair's
189
+ * successful output_write calls; the runtime (executeChair) is the ONE sealer.
190
+ * - "text" (default, and every injected-run test): the chair prints final-text JSON, which the
191
+ * invoker extracts and hands back for the runtime to seal at its boundary. Unchanged legacy
192
+ * behaviour, so a bare/test invoker is unaffected.
193
+ */
194
+ sealVia?: "text" | "output_write" | undefined;
118
195
  }
119
196
  /**
120
197
  * Default ceiling for a prompt carried as a command-line argument.