toolroll 0.9.34 → 0.9.37
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/CHANGELOG.md +102 -0
- package/README.md +7 -0
- package/dist/assignment-adapters.d.ts +12 -48
- package/dist/assignment-adapters.js +33 -43
- package/dist/assignment.d.ts +1 -0
- package/dist/assignment.js +2 -2
- package/dist/browser-workspace.d.ts +3 -3
- package/dist/build-review.d.ts +1 -1
- package/dist/build-review.js +2 -1
- package/dist/builder.js +4 -7
- package/dist/chat-acceptance.d.ts +2 -2
- package/dist/chat-actions.d.ts +18 -12
- package/dist/chat-actions.js +15 -65
- package/dist/chat-ask.js +5 -5
- package/dist/chat-decide.d.ts +14 -1
- package/dist/chat-decide.js +12 -6
- package/dist/chat-delivery-state.d.ts +12 -70
- package/dist/chat-delivery-state.js +17 -135
- package/dist/chat-delivery.js +58 -30
- package/dist/chat-flow.js +8 -6
- package/dist/chat-inbox.js +1 -1
- package/dist/coding-context.d.ts +2 -39
- package/dist/coding-context.js +2 -1
- package/dist/coding-handoff.d.ts +6 -25
- package/dist/coding-handoff.js +11 -5
- package/dist/coding-types.d.ts +2 -26
- package/dist/coding-workspace.d.ts +2 -0
- package/dist/coding-workspace.js +50 -14
- package/dist/contracts/acceptance-terms.d.ts +14 -0
- package/dist/contracts/acceptance-terms.js +16 -0
- package/dist/contracts/chat-actions.d.ts +328 -0
- package/dist/contracts/chat-actions.js +133 -0
- package/dist/contracts/chat-callback-rows.d.ts +59 -0
- package/dist/contracts/chat-callback-rows.js +46 -0
- package/dist/contracts/chat-content.d.ts +276 -0
- package/dist/contracts/chat-content.js +127 -0
- package/dist/contracts/chat-tables.d.ts +6 -0
- package/dist/contracts/chat-tables.js +136 -0
- package/dist/contracts/coding-context.d.ts +97 -0
- package/dist/contracts/coding-context.js +60 -0
- package/dist/contracts/coding-handoff.d.ts +44 -0
- package/dist/contracts/coding-handoff.js +47 -0
- package/dist/contracts/coding-workspace.d.ts +165 -0
- package/dist/contracts/coding-workspace.js +71 -0
- package/dist/contracts/contract.d.ts +40 -7
- package/dist/contracts/contract.js +155 -19
- package/dist/contracts/decision.d.ts +45 -0
- package/dist/contracts/decision.js +45 -0
- package/dist/contracts/discord-callback.d.ts +45 -0
- package/dist/contracts/discord-callback.js +34 -0
- package/dist/contracts/flow-card.d.ts +50 -0
- package/dist/contracts/flow-card.js +46 -0
- package/dist/contracts/flow-propose.d.ts +325 -0
- package/dist/contracts/flow-propose.js +41 -0
- package/dist/contracts/flow-send.d.ts +119 -0
- package/dist/contracts/flow-send.js +112 -0
- package/dist/contracts/flow.d.ts +19405 -0
- package/dist/contracts/flow.js +364 -0
- package/dist/contracts/gateway-tools.d.ts +374 -0
- package/dist/contracts/gateway-tools.js +94 -0
- package/dist/contracts/handoff.d.ts +91 -0
- package/dist/contracts/handoff.js +82 -0
- package/dist/contracts/lead-context.d.ts +165 -0
- package/dist/contracts/lead-context.js +73 -0
- package/dist/contracts/lead-tools.d.ts +1905 -0
- package/dist/contracts/lead-tools.js +265 -0
- package/dist/contracts/memory-pass.d.ts +158 -0
- package/dist/contracts/memory-pass.js +102 -0
- package/dist/contracts/project-knowledge.d.ts +126 -0
- package/dist/contracts/project-knowledge.js +93 -0
- package/dist/contracts/project-memory.d.ts +117 -0
- package/dist/contracts/project-memory.js +51 -0
- package/dist/contracts/project-skills.d.ts +121 -0
- package/dist/contracts/project-skills.js +76 -0
- package/dist/contracts/proof.d.ts +136 -0
- package/dist/contracts/proof.js +110 -0
- package/dist/contracts/review-findings.d.ts +46 -0
- package/dist/contracts/review-findings.js +48 -0
- package/dist/contracts/route.d.ts +484 -0
- package/dist/contracts/route.js +128 -0
- package/dist/contracts/scope.d.ts +105 -0
- package/dist/contracts/scope.js +114 -0
- package/dist/contracts/scout-report.d.ts +234 -0
- package/dist/contracts/scout-report.js +141 -0
- package/dist/contracts/slack-callback.d.ts +43 -0
- package/dist/contracts/slack-callback.js +35 -0
- package/dist/contracts/sort-answer.d.ts +83 -0
- package/dist/contracts/sort-answer.js +77 -0
- package/dist/contracts/stage-output.d.ts +127 -0
- package/dist/contracts/stage-output.js +166 -0
- package/dist/contracts/task-sizing.d.ts +24 -0
- package/dist/contracts/task-sizing.js +28 -0
- package/dist/contracts/teammate-turn.d.ts +43 -0
- package/dist/contracts/teammate-turn.js +51 -0
- package/dist/contracts/teams-callback.d.ts +14 -0
- package/dist/contracts/teams-callback.js +14 -0
- package/dist/contracts/telegram-callback.d.ts +120 -0
- package/dist/contracts/telegram-callback.js +97 -0
- package/dist/contracts/verification-receipt.d.ts +148 -0
- package/dist/contracts/verification-receipt.js +87 -0
- package/dist/decision.d.ts +11 -20
- package/dist/decision.js +86 -167
- package/dist/demo.js +28 -11
- package/dist/discord-chat.js +31 -20
- package/dist/evidence.d.ts +3 -40
- package/dist/evidence.js +5 -1
- package/dist/flow-engine.js +14 -17
- package/dist/flow-gallery.d.ts +3 -2
- package/dist/flow-items.d.ts +4 -13
- package/dist/flow-items.js +1 -15
- package/dist/flow-send.d.ts +6 -37
- package/dist/flow-send.js +18 -14
- package/dist/flow-share.d.ts +10 -32
- package/dist/flow-share.js +72 -84
- package/dist/flow-sort.d.ts +7 -26
- package/dist/flow-sort.js +25 -20
- package/dist/flow-starters.d.ts +2 -1
- package/dist/flow-steps.js +3 -2
- package/dist/flow-triggers.d.ts +15 -72
- package/dist/flow-triggers.js +94 -83
- package/dist/flows.d.ts +37 -220
- package/dist/flows.js +356 -387
- package/dist/invoke.js +4 -4
- package/dist/lead-context.d.ts +4 -2
- package/dist/lead-context.js +24 -17
- package/dist/mate-tools.d.ts +4 -0
- package/dist/mate-tools.js +111 -308
- package/dist/mcp-connect.d.ts +27 -4
- package/dist/mcp-connect.js +92 -17
- package/dist/mcp.js +84 -216
- package/dist/memory-pass.d.ts +17 -42
- package/dist/memory-pass.js +54 -26
- package/dist/operate.js +31 -2
- package/dist/phase-routing.d.ts +19 -72
- package/dist/phase-routing.js +34 -164
- package/dist/plan.d.ts +5 -5
- package/dist/plan.js +5 -25
- package/dist/process-recovery-eligibility.d.ts +2 -2
- package/dist/process-recovery-eligibility.js +13 -8
- package/dist/project-knowledge.d.ts +16 -35
- package/dist/project-knowledge.js +42 -21
- package/dist/project-memory.d.ts +2 -28
- package/dist/project-memory.js +28 -13
- package/dist/project-skills.d.ts +16 -38
- package/dist/project-skills.js +41 -32
- package/dist/project-tools.d.ts +1 -0
- package/dist/project-tools.js +4 -4
- package/dist/proof.d.ts +15 -91
- package/dist/proof.js +90 -249
- package/dist/provider.d.ts +2 -0
- package/dist/provider.js +4 -16
- package/dist/result-evidence-readers.d.ts +42 -0
- package/dist/result-evidence-readers.js +91 -0
- package/dist/result-shots.d.ts +1 -1
- package/dist/result-shots.js +2 -1
- package/dist/review-switch.d.ts +3 -8
- package/dist/reviewer.d.ts +8 -12
- package/dist/reviewer.js +11 -35
- package/dist/scope.d.ts +21 -43
- package/dist/scope.js +42 -128
- package/dist/scout-report.d.ts +9 -194
- package/dist/scout-report.js +113 -264
- package/dist/scout.js +15 -11
- package/dist/serve.js +17 -101
- package/dist/slack-chat.js +26 -20
- package/dist/slack-state.js +2 -1
- package/dist/store.d.ts +9 -32
- package/dist/store.js +18 -15
- package/dist/structured-output.js +2 -1
- package/dist/subscription-chat.d.ts +4 -0
- package/dist/subscription-chat.js +4 -1
- package/dist/surface.js +8 -1
- package/dist/task-review-brief.d.ts +69 -0
- package/dist/task-review-brief.js +179 -0
- package/dist/task-sizing.d.ts +3 -27
- package/dist/task-sizing.js +11 -26
- package/dist/task-text.d.ts +0 -5
- package/dist/task-text.js +1 -3
- package/dist/teammate-question.js +7 -5
- package/dist/teammate-work.js +9 -5
- package/dist/teammates.d.ts +13 -64
- package/dist/teammates.js +32 -33
- package/dist/teams-chat.js +20 -11
- package/dist/telegram-flow.js +4 -3
- package/dist/telegram-mate.d.ts +4 -14
- package/dist/telegram-mate.js +8 -7
- package/dist/telegram-team.d.ts +2 -19
- package/dist/telegram.d.ts +1 -1
- package/dist/telegram.js +52 -42
- package/dist/text-limits.d.ts +212 -1
- package/dist/text-limits.js +214 -1
- package/dist/tools-ui.d.ts +2 -1
- package/dist/tools-ui.js +7 -4
- package/dist/verification-evidence.js +24 -12
- package/package.json +1 -1
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The machine verification receipt (`verification-receipt.json`): one schema for what `sealVerificationReceipt`
|
|
3
|
+
* writes and what `verificationEvidence` reads back. Only the machine writes it, from typed values, so every receipt
|
|
4
|
+
* ever sealed already has exactly this shape; the bindings that need the store — the run, its candidate, the approved
|
|
5
|
+
* command, the retained log and a reused gate's source — are checked in plain code after parsing.
|
|
6
|
+
*
|
|
7
|
+
* Version 1 is a gate executed in its own run. Version 2 added `reusedFrom` and `executedHere: false` for a gate
|
|
8
|
+
* reused by an unchanged observation follow-up. A direct receipt is still sealed as version 1, so its bytes (and the
|
|
9
|
+
* digests bound to them) read the same in every Toolroll that reads receipts.
|
|
10
|
+
*/
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
import { type ContractResult } from "./contract.js";
|
|
13
|
+
export declare const VERIFICATION_FAILURES: readonly ["spawn-failed", "timed-out", "dependency-missing", "setup-stale", "setup-failed", "own-install-failed", "tracked-files-changed", "setup-changed-files", "checkout-moved", "cleanliness-unavailable", "dependency-still-missing", "retry-spawn-failed", "retry-timed-out", "custody-lost"];
|
|
14
|
+
/**
|
|
15
|
+
* What the repository's approved verification command did: not configured; configured but the run could not attempt
|
|
16
|
+
* it (`attemptFailed`, which downgrades to `short` rather than `refuted`: "we could not check" is not "the claim is
|
|
17
|
+
* false"); or ran, with its exit code.
|
|
18
|
+
*/
|
|
19
|
+
export declare const verifyCommandFactsSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
20
|
+
configured: z.ZodLiteral<false>;
|
|
21
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
22
|
+
configured: z.ZodLiteral<true>;
|
|
23
|
+
ran: z.ZodLiteral<false>;
|
|
24
|
+
attemptFailed: z.ZodLiteral<true>;
|
|
25
|
+
failure: z.ZodOptional<z.ZodEnum<{
|
|
26
|
+
"checkout-moved": "checkout-moved";
|
|
27
|
+
"cleanliness-unavailable": "cleanliness-unavailable";
|
|
28
|
+
"custody-lost": "custody-lost";
|
|
29
|
+
"dependency-missing": "dependency-missing";
|
|
30
|
+
"dependency-still-missing": "dependency-still-missing";
|
|
31
|
+
"own-install-failed": "own-install-failed";
|
|
32
|
+
"retry-spawn-failed": "retry-spawn-failed";
|
|
33
|
+
"retry-timed-out": "retry-timed-out";
|
|
34
|
+
"setup-changed-files": "setup-changed-files";
|
|
35
|
+
"setup-failed": "setup-failed";
|
|
36
|
+
"setup-stale": "setup-stale";
|
|
37
|
+
"spawn-failed": "spawn-failed";
|
|
38
|
+
"timed-out": "timed-out";
|
|
39
|
+
"tracked-files-changed": "tracked-files-changed";
|
|
40
|
+
}>>;
|
|
41
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
42
|
+
configured: z.ZodLiteral<true>;
|
|
43
|
+
ran: z.ZodLiteral<true>;
|
|
44
|
+
exitCode: z.ZodInt;
|
|
45
|
+
setupReplayed: z.ZodOptional<z.ZodLiteral<true>>;
|
|
46
|
+
}, z.core.$strict>]>;
|
|
47
|
+
export type VerifyCommandFacts = z.infer<typeof verifyCommandFactsSchema>;
|
|
48
|
+
/** The approved verification grant the gate ran, exactly as the store held it. */
|
|
49
|
+
export declare const receiptCommandSchema: z.ZodObject<{
|
|
50
|
+
id: z.ZodInt;
|
|
51
|
+
repo: z.ZodString;
|
|
52
|
+
command: z.ZodString;
|
|
53
|
+
timeoutMs: z.ZodInt;
|
|
54
|
+
digest: z.ZodString;
|
|
55
|
+
recoverySetupDigest: z.ZodNullable<z.ZodString>;
|
|
56
|
+
approvedBy: z.ZodString;
|
|
57
|
+
approvedAt: z.ZodString;
|
|
58
|
+
revokedAt: z.ZodNullable<z.ZodString>;
|
|
59
|
+
revokedBy: z.ZodNullable<z.ZodString>;
|
|
60
|
+
}, z.core.$strict>;
|
|
61
|
+
/** The retained check log the receipt binds, by artifact id and content. */
|
|
62
|
+
export declare const receiptLogSchema: z.ZodObject<{
|
|
63
|
+
artifactId: z.ZodInt;
|
|
64
|
+
sha256: z.ZodString;
|
|
65
|
+
bytesStored: z.ZodInt;
|
|
66
|
+
bytesOriginal: z.ZodInt;
|
|
67
|
+
truncated: z.ZodBoolean;
|
|
68
|
+
redacted: z.ZodBoolean;
|
|
69
|
+
captureStatus: z.ZodNullable<z.ZodEnum<{
|
|
70
|
+
failed: "failed";
|
|
71
|
+
ok: "ok";
|
|
72
|
+
}>>;
|
|
73
|
+
}, z.core.$strict>;
|
|
74
|
+
export declare const RECEIPT_VERSION = 2;
|
|
75
|
+
/** The receipt as read: a reused gate names its source run and digest; a direct one names neither. */
|
|
76
|
+
export declare const verificationReceiptSchema: z.ZodObject<{
|
|
77
|
+
version: z.ZodLiteral<2>;
|
|
78
|
+
run: z.ZodInt;
|
|
79
|
+
head: z.ZodString;
|
|
80
|
+
base: z.ZodNullable<z.ZodString>;
|
|
81
|
+
scopeDigest: z.ZodNullable<z.ZodString>;
|
|
82
|
+
reusedFrom: z.ZodOptional<z.ZodObject<{
|
|
83
|
+
run: z.ZodInt;
|
|
84
|
+
digest: z.ZodString;
|
|
85
|
+
}, z.core.$strict>>;
|
|
86
|
+
executedHere: z.ZodOptional<z.ZodLiteral<false>>;
|
|
87
|
+
command: z.ZodObject<{
|
|
88
|
+
id: z.ZodInt;
|
|
89
|
+
repo: z.ZodString;
|
|
90
|
+
command: z.ZodString;
|
|
91
|
+
timeoutMs: z.ZodInt;
|
|
92
|
+
digest: z.ZodString;
|
|
93
|
+
recoverySetupDigest: z.ZodNullable<z.ZodString>;
|
|
94
|
+
approvedBy: z.ZodString;
|
|
95
|
+
approvedAt: z.ZodString;
|
|
96
|
+
revokedAt: z.ZodNullable<z.ZodString>;
|
|
97
|
+
revokedBy: z.ZodNullable<z.ZodString>;
|
|
98
|
+
}, z.core.$strict>;
|
|
99
|
+
result: z.ZodUnion<readonly [z.ZodObject<{
|
|
100
|
+
configured: z.ZodLiteral<false>;
|
|
101
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
102
|
+
configured: z.ZodLiteral<true>;
|
|
103
|
+
ran: z.ZodLiteral<false>;
|
|
104
|
+
attemptFailed: z.ZodLiteral<true>;
|
|
105
|
+
failure: z.ZodOptional<z.ZodEnum<{
|
|
106
|
+
"checkout-moved": "checkout-moved";
|
|
107
|
+
"cleanliness-unavailable": "cleanliness-unavailable";
|
|
108
|
+
"custody-lost": "custody-lost";
|
|
109
|
+
"dependency-missing": "dependency-missing";
|
|
110
|
+
"dependency-still-missing": "dependency-still-missing";
|
|
111
|
+
"own-install-failed": "own-install-failed";
|
|
112
|
+
"retry-spawn-failed": "retry-spawn-failed";
|
|
113
|
+
"retry-timed-out": "retry-timed-out";
|
|
114
|
+
"setup-changed-files": "setup-changed-files";
|
|
115
|
+
"setup-failed": "setup-failed";
|
|
116
|
+
"setup-stale": "setup-stale";
|
|
117
|
+
"spawn-failed": "spawn-failed";
|
|
118
|
+
"timed-out": "timed-out";
|
|
119
|
+
"tracked-files-changed": "tracked-files-changed";
|
|
120
|
+
}>>;
|
|
121
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
122
|
+
configured: z.ZodLiteral<true>;
|
|
123
|
+
ran: z.ZodLiteral<true>;
|
|
124
|
+
exitCode: z.ZodInt;
|
|
125
|
+
setupReplayed: z.ZodOptional<z.ZodLiteral<true>>;
|
|
126
|
+
}, z.core.$strict>]>;
|
|
127
|
+
log: z.ZodObject<{
|
|
128
|
+
artifactId: z.ZodInt;
|
|
129
|
+
sha256: z.ZodString;
|
|
130
|
+
bytesStored: z.ZodInt;
|
|
131
|
+
bytesOriginal: z.ZodInt;
|
|
132
|
+
truncated: z.ZodBoolean;
|
|
133
|
+
redacted: z.ZodBoolean;
|
|
134
|
+
captureStatus: z.ZodNullable<z.ZodEnum<{
|
|
135
|
+
failed: "failed";
|
|
136
|
+
ok: "ok";
|
|
137
|
+
}>>;
|
|
138
|
+
}, z.core.$strict>;
|
|
139
|
+
}, z.core.$strict>;
|
|
140
|
+
export type VerificationReceipt = z.infer<typeof verificationReceiptSchema>;
|
|
141
|
+
/** A version 1 receipt read as version 2: the same fields. Reuse is a version 2 field; a version 1 receipt naming it
|
|
142
|
+
* is refused by its reader, which still knows the version it was sealed as. */
|
|
143
|
+
export declare function upgradeDirectReceipt(body: Record<string, unknown>): Record<string, unknown>;
|
|
144
|
+
export declare const RECEIPT_UPGRADES: {
|
|
145
|
+
readonly 1: typeof upgradeDirectReceipt;
|
|
146
|
+
};
|
|
147
|
+
/** Read a receipt: version 2 as itself, version 1 as a direct gate, a newer one refused plainly. */
|
|
148
|
+
export declare function readVerificationReceipt(input: unknown): ContractResult<VerificationReceipt>;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The machine verification receipt (`verification-receipt.json`): one schema for what `sealVerificationReceipt`
|
|
3
|
+
* writes and what `verificationEvidence` reads back. Only the machine writes it, from typed values, so every receipt
|
|
4
|
+
* ever sealed already has exactly this shape; the bindings that need the store — the run, its candidate, the approved
|
|
5
|
+
* command, the retained log and a reused gate's source — are checked in plain code after parsing.
|
|
6
|
+
*
|
|
7
|
+
* Version 1 is a gate executed in its own run. Version 2 added `reusedFrom` and `executedHere: false` for a gate
|
|
8
|
+
* reused by an unchanged observation follow-up. A direct receipt is still sealed as version 1, so its bytes (and the
|
|
9
|
+
* digests bound to them) read the same in every Toolroll that reads receipts.
|
|
10
|
+
*/
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
import { readVersioned, versioned } from "./contract.js";
|
|
13
|
+
export const VERIFICATION_FAILURES = [
|
|
14
|
+
"spawn-failed",
|
|
15
|
+
"timed-out",
|
|
16
|
+
"dependency-missing",
|
|
17
|
+
"setup-stale",
|
|
18
|
+
"setup-failed",
|
|
19
|
+
"own-install-failed",
|
|
20
|
+
"tracked-files-changed",
|
|
21
|
+
"setup-changed-files",
|
|
22
|
+
"checkout-moved",
|
|
23
|
+
"cleanliness-unavailable",
|
|
24
|
+
"dependency-still-missing",
|
|
25
|
+
"retry-spawn-failed",
|
|
26
|
+
"retry-timed-out",
|
|
27
|
+
"custody-lost",
|
|
28
|
+
];
|
|
29
|
+
/**
|
|
30
|
+
* What the repository's approved verification command did: not configured; configured but the run could not attempt
|
|
31
|
+
* it (`attemptFailed`, which downgrades to `short` rather than `refuted`: "we could not check" is not "the claim is
|
|
32
|
+
* false"); or ran, with its exit code.
|
|
33
|
+
*/
|
|
34
|
+
export const verifyCommandFactsSchema = z.union([
|
|
35
|
+
z.strictObject({ configured: z.literal(false) }),
|
|
36
|
+
z.strictObject({ configured: z.literal(true), ran: z.literal(false), attemptFailed: z.literal(true), failure: z.enum(VERIFICATION_FAILURES).optional() }),
|
|
37
|
+
z.strictObject({ configured: z.literal(true), ran: z.literal(true), exitCode: z.int().min(0).max(255), setupReplayed: z.literal(true).optional() }),
|
|
38
|
+
]);
|
|
39
|
+
/** The approved verification grant the gate ran, exactly as the store held it. */
|
|
40
|
+
export const receiptCommandSchema = z.strictObject({
|
|
41
|
+
id: z.int(),
|
|
42
|
+
repo: z.string(),
|
|
43
|
+
command: z.string(),
|
|
44
|
+
timeoutMs: z.int(),
|
|
45
|
+
digest: z.string(),
|
|
46
|
+
recoverySetupDigest: z.string().nullable(),
|
|
47
|
+
approvedBy: z.string(),
|
|
48
|
+
approvedAt: z.string(),
|
|
49
|
+
revokedAt: z.string().nullable(),
|
|
50
|
+
revokedBy: z.string().nullable(),
|
|
51
|
+
});
|
|
52
|
+
/** The retained check log the receipt binds, by artifact id and content. */
|
|
53
|
+
export const receiptLogSchema = z.strictObject({
|
|
54
|
+
artifactId: z.int(),
|
|
55
|
+
sha256: z.string(),
|
|
56
|
+
bytesStored: z.int(),
|
|
57
|
+
bytesOriginal: z.int(),
|
|
58
|
+
truncated: z.boolean(),
|
|
59
|
+
redacted: z.boolean(),
|
|
60
|
+
captureStatus: z.enum(["ok", "failed"]).nullable(),
|
|
61
|
+
});
|
|
62
|
+
export const RECEIPT_VERSION = 2;
|
|
63
|
+
const receiptFields = {
|
|
64
|
+
run: z.int(),
|
|
65
|
+
head: z.string(),
|
|
66
|
+
base: z.string().nullable(),
|
|
67
|
+
scopeDigest: z.string().nullable(),
|
|
68
|
+
};
|
|
69
|
+
/** The receipt as read: a reused gate names its source run and digest; a direct one names neither. */
|
|
70
|
+
export const verificationReceiptSchema = versioned(RECEIPT_VERSION, {
|
|
71
|
+
...receiptFields,
|
|
72
|
+
reusedFrom: z.strictObject({ run: z.int(), digest: z.string() }).optional(),
|
|
73
|
+
executedHere: z.literal(false).optional(),
|
|
74
|
+
command: receiptCommandSchema,
|
|
75
|
+
result: verifyCommandFactsSchema,
|
|
76
|
+
log: receiptLogSchema,
|
|
77
|
+
});
|
|
78
|
+
/** A version 1 receipt read as version 2: the same fields. Reuse is a version 2 field; a version 1 receipt naming it
|
|
79
|
+
* is refused by its reader, which still knows the version it was sealed as. */
|
|
80
|
+
export function upgradeDirectReceipt(body) {
|
|
81
|
+
return { ...body, version: RECEIPT_VERSION };
|
|
82
|
+
}
|
|
83
|
+
export const RECEIPT_UPGRADES = { 1: upgradeDirectReceipt };
|
|
84
|
+
/** Read a receipt: version 2 as itself, version 1 as a direct gate, a newer one refused plainly. */
|
|
85
|
+
export function readVerificationReceipt(input) {
|
|
86
|
+
return readVersioned(verificationReceiptSchema, input, RECEIPT_UPGRADES);
|
|
87
|
+
}
|
package/dist/decision.d.ts
CHANGED
|
@@ -14,18 +14,11 @@
|
|
|
14
14
|
* caps also keep one screen one screen: an option list that needs scrolling
|
|
15
15
|
* has already failed the milestone sentence.
|
|
16
16
|
*/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
};
|
|
23
|
-
export type ParsedDecision = {
|
|
24
|
-
urgency: "blocking";
|
|
25
|
-
recap: string;
|
|
26
|
-
question: string;
|
|
27
|
-
options: ParsedOption[];
|
|
28
|
-
recommendation: string;
|
|
17
|
+
import { type DecisionOption, type DecisionPayload } from "./contracts/decision.js";
|
|
18
|
+
/** One option as a decision keeps it (src/contracts/decision.ts). */
|
|
19
|
+
export type ParsedOption = DecisionOption;
|
|
20
|
+
/** A decision as read: the payload's fields, its text trimmed, and the optional ones present or null. */
|
|
21
|
+
export type ParsedDecision = Omit<DecisionPayload, "assignee" | "deadline"> & {
|
|
29
22
|
assignee: string | null;
|
|
30
23
|
/** Normalized to toISOString(), like every timestamp in the store. */
|
|
31
24
|
deadline: string | null;
|
|
@@ -41,17 +34,10 @@ export type ParseResult = {
|
|
|
41
34
|
ok: false;
|
|
42
35
|
problems: Problem[];
|
|
43
36
|
};
|
|
44
|
-
/** One screen's worth, enforced rather than hoped for. */
|
|
37
|
+
/** One screen's worth, enforced rather than hoped for. The payload's fields are bounded by its schema (src/contracts/decision.ts, from TEXT_LIMITS). */
|
|
45
38
|
export declare const LIMITS: {
|
|
46
39
|
/** Bytes, before parsing. Applied by the reader too; this is the backstop. */
|
|
47
40
|
readonly payload: number;
|
|
48
|
-
readonly recap: 2000;
|
|
49
|
-
readonly question: 2000;
|
|
50
|
-
readonly consequence: 500;
|
|
51
|
-
readonly label: 120;
|
|
52
|
-
readonly optionId: 40;
|
|
53
|
-
readonly assignee: 120;
|
|
54
|
-
readonly options: 6;
|
|
55
41
|
/** UTF-16 code units (TEXT_LIMITS.note: revise feedback, steering and decision notes); the byte backstop lives in validateNote. */
|
|
56
42
|
readonly note: 4000;
|
|
57
43
|
};
|
|
@@ -79,6 +65,11 @@ export declare function hasForbiddenControls(text: string): boolean;
|
|
|
79
65
|
* backwards is not a title, it is a costume.
|
|
80
66
|
*/
|
|
81
67
|
export declare function hasDisguisedText(text: string): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* A parked decision, read against its contract (src/contracts/decision.ts): fail closed, every problem at once, each
|
|
70
|
+
* naming its path (`options[1].reversible: required`) under the reason code the repair turn, incidents and tests have
|
|
71
|
+
* always used (`missing-reversible`). A decision written without `version` reads as it always did.
|
|
72
|
+
*/
|
|
82
73
|
export declare function parseDecision(raw: string): ParseResult;
|
|
83
74
|
/**
|
|
84
75
|
* The terminal handoff: how every non-parking attempt says how it ended.
|
package/dist/decision.js
CHANGED
|
@@ -14,18 +14,12 @@
|
|
|
14
14
|
* caps also keep one screen one screen: an option list that needs scrolling
|
|
15
15
|
* has already failed the milestone sentence.
|
|
16
16
|
*/
|
|
17
|
+
import { readDecisionPayload } from "./contracts/decision.js";
|
|
17
18
|
import { TEXT_LIMITS } from "./text-limits.js";
|
|
18
|
-
/** One screen's worth, enforced rather than hoped for. */
|
|
19
|
+
/** One screen's worth, enforced rather than hoped for. The payload's fields are bounded by its schema (src/contracts/decision.ts, from TEXT_LIMITS). */
|
|
19
20
|
export const LIMITS = {
|
|
20
21
|
/** Bytes, before parsing. Applied by the reader too; this is the backstop. */
|
|
21
22
|
payload: 64 * 1024,
|
|
22
|
-
recap: 2_000,
|
|
23
|
-
question: 2_000,
|
|
24
|
-
consequence: 500,
|
|
25
|
-
label: 120,
|
|
26
|
-
optionId: 40,
|
|
27
|
-
assignee: 120,
|
|
28
|
-
options: 6,
|
|
29
23
|
/** UTF-16 code units (TEXT_LIMITS.note: revise feedback, steering and decision notes); the byte backstop lives in validateNote. */
|
|
30
24
|
note: TEXT_LIMITS.note,
|
|
31
25
|
};
|
|
@@ -58,8 +52,6 @@ export function validateNote(raw) {
|
|
|
58
52
|
}
|
|
59
53
|
return { ok: true, note };
|
|
60
54
|
}
|
|
61
|
-
/** Option ids travel in URLs, CLI arguments, and CAS updates — they are identifiers, not prose. */
|
|
62
|
-
const OPTION_ID = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
|
|
63
55
|
/**
|
|
64
56
|
* Multi-line prose may contain newlines and tabs; nothing anywhere in a
|
|
65
57
|
* decision may contain the rest of C0/C1 — those are how text stops being
|
|
@@ -82,9 +74,14 @@ export function hasForbiddenControls(text) {
|
|
|
82
74
|
export function hasDisguisedText(text) {
|
|
83
75
|
return FORBIDDEN_MULTILINE.test(text) || FORBIDDEN_INVISIBLES.test(text);
|
|
84
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* A parked decision, read against its contract (src/contracts/decision.ts): fail closed, every problem at once, each
|
|
79
|
+
* naming its path (`options[1].reversible: required`) under the reason code the repair turn, incidents and tests have
|
|
80
|
+
* always used (`missing-reversible`). A decision written without `version` reads as it always did.
|
|
81
|
+
*/
|
|
85
82
|
export function parseDecision(raw) {
|
|
86
83
|
if (Buffer.byteLength(raw, "utf8") > LIMITS.payload) {
|
|
87
|
-
return refuse("too-large", `
|
|
84
|
+
return refuse("too-large", `payload: over ${LIMITS.payload} bytes — one screen does not need that`);
|
|
88
85
|
}
|
|
89
86
|
let parsed;
|
|
90
87
|
try {
|
|
@@ -94,187 +91,109 @@ export function parseDecision(raw) {
|
|
|
94
91
|
return refuse("not-json", `the payload is not JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
95
92
|
}
|
|
96
93
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
97
|
-
return refuse("not-an-object", "
|
|
94
|
+
return refuse("not-an-object", "payload: must be one JSON object");
|
|
98
95
|
}
|
|
96
|
+
const read = readDecisionPayload(parsed);
|
|
97
|
+
const contract = read.ok ? [] : read.issues.map(decisionProblemOf);
|
|
99
98
|
const body = parsed;
|
|
100
|
-
const problems = [];
|
|
101
|
-
|
|
102
|
-
// whether the loop can continue has not composed a decision. Only
|
|
103
|
-
// 'blocking' exists in M3 — advisory needs a commit-and-complete lifecycle
|
|
104
|
-
// this park-before-commit path does not have.
|
|
105
|
-
if (body["urgency"] !== "blocking") {
|
|
106
|
-
problems.push({
|
|
107
|
-
reason: "bad-urgency",
|
|
108
|
-
message: `urgency must be the string "blocking" (got ${describe(body["urgency"])})`,
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
const recap = prose(body["recap"], "recap", LIMITS.recap, problems);
|
|
112
|
-
const question = prose(body["question"], "question", LIMITS.question, problems);
|
|
113
|
-
const options = parseOptions(body["options"], problems);
|
|
114
|
-
let recommendation = null;
|
|
115
|
-
if (typeof body["recommendation"] !== "string" || body["recommendation"] === "") {
|
|
116
|
-
problems.push({
|
|
117
|
-
reason: "missing-recommendation",
|
|
118
|
-
message: "recommendation is required and must be an option id — a decision without one is a shrug",
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
else if (options !== null && !options.some(option => option.id === body["recommendation"])) {
|
|
122
|
-
problems.push({
|
|
123
|
-
reason: "bad-recommendation",
|
|
124
|
-
message: `recommendation "${truncate(String(body["recommendation"]), 60)}" does not match any option id`,
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
else {
|
|
128
|
-
recommendation = body["recommendation"];
|
|
129
|
-
}
|
|
130
|
-
let assignee = null;
|
|
131
|
-
if (body["assignee"] !== undefined && body["assignee"] !== null) {
|
|
132
|
-
const line = singleLine(body["assignee"], "assignee", LIMITS.assignee, problems);
|
|
133
|
-
if (line !== null)
|
|
134
|
-
assignee = line;
|
|
135
|
-
}
|
|
136
|
-
let deadline = null;
|
|
137
|
-
if (body["deadline"] !== undefined && body["deadline"] !== null) {
|
|
138
|
-
const stamp = typeof body["deadline"] === "string" ? Date.parse(body["deadline"]) : NaN;
|
|
139
|
-
if (Number.isNaN(stamp)) {
|
|
140
|
-
problems.push({
|
|
141
|
-
reason: "bad-deadline",
|
|
142
|
-
message: `deadline must be an ISO 8601 timestamp (got ${describe(body["deadline"])})`,
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
else {
|
|
146
|
-
// Normalized so the store's lexicographic-comparison invariant holds.
|
|
147
|
-
deadline = new Date(stamp).toISOString();
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
if (problems.length > 0)
|
|
99
|
+
const problems = [...contract, ...decisionRuleProblems(body, contract)];
|
|
100
|
+
if (!read.ok || problems.length > 0)
|
|
151
101
|
return { ok: false, problems };
|
|
102
|
+
const payload = read.value;
|
|
152
103
|
return {
|
|
153
104
|
ok: true,
|
|
154
105
|
decision: {
|
|
155
|
-
urgency:
|
|
156
|
-
recap: recap,
|
|
157
|
-
question: question,
|
|
158
|
-
options: options,
|
|
159
|
-
recommendation: recommendation,
|
|
160
|
-
assignee,
|
|
161
|
-
|
|
106
|
+
urgency: payload.urgency,
|
|
107
|
+
recap: payload.recap.trim(),
|
|
108
|
+
question: payload.question.trim(),
|
|
109
|
+
options: payload.options.map(option => ({ id: option.id, label: option.label.trim(), consequence: option.consequence.trim(), reversible: option.reversible })),
|
|
110
|
+
recommendation: recommendedId(payload.recommendation),
|
|
111
|
+
assignee: payload.assignee === undefined || payload.assignee === null ? null : payload.assignee.trim(),
|
|
112
|
+
// Normalized so the store's lexicographic-comparison invariant holds.
|
|
113
|
+
deadline: payload.deadline === undefined || payload.deadline === null ? null : new Date(Date.parse(payload.deadline)).toISOString(),
|
|
162
114
|
},
|
|
163
115
|
};
|
|
164
116
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
117
|
+
/** The reason code a contract issue has always had: `missing-recap`, `too-few-options`, `bad-option-id`. */
|
|
118
|
+
function decisionReasonOf(issue) {
|
|
119
|
+
const at = issue.path;
|
|
120
|
+
const text = (field) => (issue.kind === "too-long" ? `${field}-too-long` : `missing-${field}`);
|
|
121
|
+
switch (at) {
|
|
122
|
+
case "urgency": return "bad-urgency";
|
|
123
|
+
case "options": return issue.kind === "too-few" ? "too-few-options" : issue.kind === "too-many" ? "too-many-options" : "no-options";
|
|
124
|
+
case "recommendation": return issue.kind === "too-long" ? "bad-recommendation" : "missing-recommendation";
|
|
125
|
+
case "deadline": return "bad-deadline";
|
|
169
126
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
message: `at most ${LIMITS.options} options fit on one screen (got ${value.length})`,
|
|
181
|
-
});
|
|
182
|
-
return null;
|
|
127
|
+
const option = /^options\[(\d+)\](?:\.(\w+))?$/.exec(at);
|
|
128
|
+
if (option !== null) {
|
|
129
|
+
const [, index, field] = option;
|
|
130
|
+
if (field === undefined)
|
|
131
|
+
return "bad-option";
|
|
132
|
+
if (field === "id")
|
|
133
|
+
return "bad-option-id";
|
|
134
|
+
if (field === "reversible")
|
|
135
|
+
return "missing-reversible";
|
|
136
|
+
return text(`option-${index}-${field}`);
|
|
183
137
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
138
|
+
return text(at);
|
|
139
|
+
}
|
|
140
|
+
const decisionProblemOf = (issue) => ({ reason: decisionReasonOf(issue), message: issue.line });
|
|
141
|
+
/** True when `problems` name `field` or anything under it. */
|
|
142
|
+
const touched = (problems, field) => problems.some(problem => problem.message.startsWith(`${field}:`) || problem.message.startsWith(`${field}[`) || problem.message.startsWith(`${field}.`));
|
|
143
|
+
/**
|
|
144
|
+
* The rules JSON Schema cannot state, in plain code with named errors: text not blank once trimmed and free of control
|
|
145
|
+
* characters (a label or assignee on one line), unique option ids, a recommendation naming one of them, and a deadline
|
|
146
|
+
* that is a timestamp. Fields the contract already refused are skipped, so every problem is reported once and all at
|
|
147
|
+
* once.
|
|
148
|
+
*/
|
|
149
|
+
function decisionRuleProblems(body, contract) {
|
|
150
|
+
const problems = [];
|
|
151
|
+
const text = (value, path, slug, oneLine) => {
|
|
152
|
+
if (typeof value !== "string" || touched(contract, path))
|
|
191
153
|
return;
|
|
154
|
+
if (value.trim() === "")
|
|
155
|
+
problems.push({ reason: `missing-${slug}`, message: `${path}: must not be blank` });
|
|
156
|
+
else if ((oneLine ? FORBIDDEN_SINGLE_LINE : FORBIDDEN_MULTILINE).test(value)) {
|
|
157
|
+
problems.push({ reason: `${slug}-control-characters`, message: `${path}: ${oneLine ? "must be one line with no control characters" : "contains control characters — text only"}` });
|
|
192
158
|
}
|
|
159
|
+
};
|
|
160
|
+
text(body["recap"], "recap", "recap", false);
|
|
161
|
+
text(body["question"], "question", "question", false);
|
|
162
|
+
const options = Array.isArray(body["options"]) ? body["options"] : [];
|
|
163
|
+
const seen = new Set();
|
|
164
|
+
options.forEach((entry, index) => {
|
|
165
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry))
|
|
166
|
+
return;
|
|
193
167
|
const option = entry;
|
|
194
168
|
const id = option["id"];
|
|
195
|
-
if (typeof id
|
|
196
|
-
|
|
197
|
-
reason: "
|
|
198
|
-
message: `option ${index} needs an id of 1-${LIMITS.optionId} letters, digits, - or _ (got ${describe(id)})`,
|
|
199
|
-
});
|
|
200
|
-
sound = false;
|
|
201
|
-
}
|
|
202
|
-
else if (seen.has(id)) {
|
|
203
|
-
problems.push({ reason: "duplicate-option-id", message: `option id "${id}" appears twice` });
|
|
204
|
-
sound = false;
|
|
205
|
-
}
|
|
206
|
-
else {
|
|
169
|
+
if (typeof id === "string" && !touched(contract, `options[${index}].id`)) {
|
|
170
|
+
if (seen.has(id))
|
|
171
|
+
problems.push({ reason: "duplicate-option-id", message: `options[${index}].id: "${id}" appears twice` });
|
|
207
172
|
seen.add(id);
|
|
208
173
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
// Absent is invalid, never defaulted: a defaulted reversibility is a
|
|
212
|
-
// guess wearing a schema, and reversible=false is the field the
|
|
213
|
-
// scheduler's refusal to auto-apply rests on.
|
|
214
|
-
if (typeof option["reversible"] !== "boolean") {
|
|
215
|
-
problems.push({
|
|
216
|
-
reason: "missing-reversible",
|
|
217
|
-
message: `option ${index} must say reversible: true or false — unstated is not reversible, it is unstated`,
|
|
218
|
-
});
|
|
219
|
-
sound = false;
|
|
220
|
-
}
|
|
221
|
-
if (label === null || consequence === null) {
|
|
222
|
-
sound = false;
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
if (sound) {
|
|
226
|
-
options.push({
|
|
227
|
-
id: id,
|
|
228
|
-
label,
|
|
229
|
-
consequence,
|
|
230
|
-
reversible: option["reversible"],
|
|
231
|
-
});
|
|
232
|
-
}
|
|
174
|
+
text(option["label"], `options[${index}].label`, `option-${index}-label`, true);
|
|
175
|
+
text(option["consequence"], `options[${index}].consequence`, `option-${index}-consequence`, false);
|
|
233
176
|
});
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
if (typeof value !== "string" || value.trim() === "") {
|
|
238
|
-
problems.push({ reason: `missing-${slug(field)}`, message: `${field} is required` });
|
|
239
|
-
return null;
|
|
177
|
+
const recommendation = body["recommendation"];
|
|
178
|
+
if (typeof recommendation === "string" && !touched(contract, "recommendation") && !touched(contract, "options") && !options.some(option => typeof option === "object" && option !== null && option["id"] === recommendedId(recommendation))) {
|
|
179
|
+
problems.push({ reason: "bad-recommendation", message: `recommendation: "${truncate(recommendation, 60)}" does not match any option id` });
|
|
240
180
|
}
|
|
241
|
-
if (
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
problems.push({
|
|
247
|
-
reason: `${slug(field)}-control-characters`,
|
|
248
|
-
message: `${field} contains control characters — text only`,
|
|
249
|
-
});
|
|
250
|
-
return null;
|
|
181
|
+
if (body["assignee"] !== null)
|
|
182
|
+
text(body["assignee"], "assignee", "assignee", true);
|
|
183
|
+
const deadline = body["deadline"];
|
|
184
|
+
if (typeof deadline === "string" && !touched(contract, "deadline") && Number.isNaN(Date.parse(deadline))) {
|
|
185
|
+
problems.push({ reason: "bad-deadline", message: `deadline: must be an ISO 8601 timestamp (got ${describe(deadline)})` });
|
|
251
186
|
}
|
|
252
|
-
return
|
|
187
|
+
return problems;
|
|
253
188
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
}
|
|
259
|
-
if (value.length > cap) {
|
|
260
|
-
problems.push({ reason: `${slug(field)}-too-long`, message: `${field} is over ${cap} characters` });
|
|
261
|
-
return null;
|
|
262
|
-
}
|
|
263
|
-
if (FORBIDDEN_SINGLE_LINE.test(value)) {
|
|
264
|
-
problems.push({
|
|
265
|
-
reason: `${slug(field)}-control-characters`,
|
|
266
|
-
message: `${field} must be one line with no control characters`,
|
|
267
|
-
});
|
|
268
|
-
return null;
|
|
269
|
-
}
|
|
270
|
-
return value.trim();
|
|
189
|
+
/** The option id a recommendation names: a bare id, or the id before the first ":" in "<id>: why". */
|
|
190
|
+
function recommendedId(recommendation) {
|
|
191
|
+
const colon = recommendation.indexOf(":");
|
|
192
|
+
return (colon === -1 ? recommendation : recommendation.slice(0, colon)).trim();
|
|
271
193
|
}
|
|
272
194
|
function refuse(reason, message) {
|
|
273
195
|
return { ok: false, problems: [{ reason, message }] };
|
|
274
196
|
}
|
|
275
|
-
function slug(field) {
|
|
276
|
-
return field.replace(/[^a-z0-9]+/gi, "-").toLowerCase();
|
|
277
|
-
}
|
|
278
197
|
function describe(value) {
|
|
279
198
|
if (value === undefined)
|
|
280
199
|
return "nothing";
|