toolroll 0.9.40 → 0.9.42

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 (68) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/cli.js +3 -1
  3. package/dist/coding-types.d.ts +2 -24
  4. package/dist/coding-workspace.js +12 -11
  5. package/dist/contracts/cli.d.ts +736 -0
  6. package/dist/contracts/cli.js +223 -0
  7. package/dist/contracts/coding-activity.d.ts +64 -0
  8. package/dist/contracts/coding-activity.js +58 -0
  9. package/dist/contracts/console-api.d.ts +483 -0
  10. package/dist/contracts/console-api.js +273 -0
  11. package/dist/contracts/diff-stat.d.ts +58 -0
  12. package/dist/contracts/diff-stat.js +59 -0
  13. package/dist/contracts/integration-metadata.d.ts +99 -0
  14. package/dist/contracts/integration-metadata.js +101 -0
  15. package/dist/contracts/model-catalog.d.ts +21 -0
  16. package/dist/contracts/model-catalog.js +14 -0
  17. package/dist/contracts/native-census.d.ts +42 -0
  18. package/dist/contracts/native-census.js +36 -0
  19. package/dist/contracts/observation-request.d.ts +37 -0
  20. package/dist/contracts/observation-request.js +25 -0
  21. package/dist/contracts/provider.d.ts +30 -0
  22. package/dist/contracts/provider.js +15 -0
  23. package/dist/contracts/recipes.d.ts +166 -0
  24. package/dist/contracts/recipes.js +52 -0
  25. package/dist/contracts/retention.d.ts +42 -0
  26. package/dist/contracts/retention.js +24 -0
  27. package/dist/contracts/storage.d.ts +23 -0
  28. package/dist/contracts/storage.js +10 -0
  29. package/dist/contracts/store-json.d.ts +315 -0
  30. package/dist/contracts/store-json.js +196 -0
  31. package/dist/contracts/trigger-payloads.d.ts +369 -0
  32. package/dist/contracts/trigger-payloads.js +144 -0
  33. package/dist/contracts/update-journal.d.ts +254 -0
  34. package/dist/contracts/update-journal.js +155 -0
  35. package/dist/contracts/verification-view.d.ts +93 -0
  36. package/dist/contracts/verification-view.js +43 -0
  37. package/dist/control-setup.d.ts +1 -1
  38. package/dist/desktop-update.d.ts +7 -40
  39. package/dist/desktop-update.js +36 -8
  40. package/dist/evidence.d.ts +3 -19
  41. package/dist/flow-triggers.js +61 -60
  42. package/dist/mcp-connect.d.ts +0 -1
  43. package/dist/mcp-connect.js +41 -23
  44. package/dist/model-catalog.js +8 -6
  45. package/dist/observations.d.ts +2 -6
  46. package/dist/observations.js +13 -9
  47. package/dist/operate.js +2 -1
  48. package/dist/process-recovery-native.d.ts +2 -13
  49. package/dist/process-recovery-native.js +8 -5
  50. package/dist/project-tools.d.ts +6 -24
  51. package/dist/project-tools.js +22 -18
  52. package/dist/provider.d.ts +3 -8
  53. package/dist/provider.js +15 -11
  54. package/dist/recipes.d.ts +3 -28
  55. package/dist/recipes.js +79 -52
  56. package/dist/retention.d.ts +4 -5
  57. package/dist/retention.js +5 -4
  58. package/dist/serve.js +142 -28
  59. package/dist/storage.d.ts +2 -1
  60. package/dist/storage.js +12 -9
  61. package/dist/store.d.ts +3 -15
  62. package/dist/store.js +80 -158
  63. package/dist/surface.d.ts +6 -54
  64. package/dist/surface.js +6 -1
  65. package/dist/toolroll-update.d.ts +8 -99
  66. package/dist/toolroll-update.js +28 -18
  67. package/dist/verification-evidence.js +27 -12
  68. package/package.json +1 -1
@@ -0,0 +1,14 @@
1
+ /**
2
+ * What the model catalog (model-catalog.ts) reads from outside Toolroll: the Codex CLI's own model list
3
+ * (`~/.codex/models_cache.json`) and npm's latest-version answer for each agent CLI. Both belong to someone else, so
4
+ * both are read loosely: unknown keys are ignored, an entry that doesn't fit is skipped, and a file or answer that
5
+ * doesn't fit at all reads as nothing (an empty list, no version) — never an error.
6
+ */
7
+ import { z } from "zod";
8
+ import { modelIdSchema } from "./provider.js";
9
+ /** One listed Codex model: a usable id, shown in the picker. Its display name is used when it is text. */
10
+ export const codexModelSchema = z.object({ slug: modelIdSchema, visibility: z.literal("list"), display_name: z.unknown().optional() });
11
+ /** The Codex cache file: its `models` list, entries read one by one with `codexModelSchema`. */
12
+ export const codexModelsCacheSchema = z.object({ models: z.array(z.unknown()) });
13
+ /** npm's `/<package>/latest` answer: a version starting with major.minor.patch. */
14
+ export const npmLatestSchema = z.object({ version: z.string().regex(/^\d+\.\d+\.\d+/) });
@@ -0,0 +1,42 @@
1
+ /**
2
+ * The macOS process census the recovery helper prints (docs/plans/zod-revamp.md, item 19): `schema: 1`, the boot,
3
+ * the collector, and one identity per process. The parser in process-recovery-native.ts reads it with these schemas
4
+ * and keeps its fail-closed reason codes: a top-level problem is `malformed-native-census`, one in a process row
5
+ * `malformed-process-identity`. Duplicate pids and unique ids, the 64-bit bound on ids and completeness run in plain
6
+ * code after parsing. Unknown keys are dropped, and each identity is rebuilt in its sealed key order.
7
+ */
8
+ import { z } from "zod";
9
+ export declare const NATIVE_CENSUS_ERRORS: readonly ["kernel-process-table-unreadable", "process-membership-unreadable", "process-identity-unreadable", "boot-identity-unreadable", "coalition-counters-unreadable", "coalition-counters-changed", "coalition-count-mismatch", "anchor-identity-unreadable"];
10
+ export declare const darwinProcessIdentitySchema: z.ZodObject<{
11
+ pid: z.ZodInt;
12
+ ppid: z.ZodNullable<z.ZodInt>;
13
+ uid: z.ZodNullable<z.ZodInt>;
14
+ birthMs: z.ZodNullable<z.ZodNumber>;
15
+ uniqueId: z.ZodNullable<z.ZodString>;
16
+ parentUniqueId: z.ZodNullable<z.ZodString>;
17
+ traced: z.ZodNullable<z.ZodBoolean>;
18
+ executable: z.ZodNullable<z.ZodString>;
19
+ originalParentVersion: z.ZodNullable<z.ZodInt>;
20
+ pidVersion: z.ZodExactOptional<z.ZodInt>;
21
+ }, z.core.$strip>;
22
+ export type DarwinProcessIdentity = z.infer<typeof darwinProcessIdentitySchema>;
23
+ /** Top level only: process rows are read one by one, so a bad row gets its own reason. */
24
+ export declare const darwinNativeCensusSchema: z.ZodObject<{
25
+ schema: z.ZodLiteral<1>;
26
+ bootId: z.ZodNullable<z.ZodString>;
27
+ collectorPid: z.ZodInt;
28
+ complete: z.ZodBoolean;
29
+ processes: z.ZodArray<z.ZodUnknown>;
30
+ errors: z.ZodArray<z.ZodEnum<{
31
+ "anchor-identity-unreadable": "anchor-identity-unreadable";
32
+ "boot-identity-unreadable": "boot-identity-unreadable";
33
+ "coalition-count-mismatch": "coalition-count-mismatch";
34
+ "coalition-counters-changed": "coalition-counters-changed";
35
+ "coalition-counters-unreadable": "coalition-counters-unreadable";
36
+ "kernel-process-table-unreadable": "kernel-process-table-unreadable";
37
+ "process-identity-unreadable": "process-identity-unreadable";
38
+ "process-membership-unreadable": "process-membership-unreadable";
39
+ }>>;
40
+ }, z.core.$strip>;
41
+ /** A kernel id within 64 bits (the schema checks the digits). */
42
+ export declare const kernelIdInRange: (id: string) => boolean;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The macOS process census the recovery helper prints (docs/plans/zod-revamp.md, item 19): `schema: 1`, the boot,
3
+ * the collector, and one identity per process. The parser in process-recovery-native.ts reads it with these schemas
4
+ * and keeps its fail-closed reason codes: a top-level problem is `malformed-native-census`, one in a process row
5
+ * `malformed-process-identity`. Duplicate pids and unique ids, the 64-bit bound on ids and completeness run in plain
6
+ * code after parsing. Unknown keys are dropped, and each identity is rebuilt in its sealed key order.
7
+ */
8
+ import { z } from "zod";
9
+ export const NATIVE_CENSUS_ERRORS = ["kernel-process-table-unreadable", "process-membership-unreadable", "process-identity-unreadable", "boot-identity-unreadable", "coalition-counters-unreadable", "coalition-counters-changed", "coalition-count-mismatch", "anchor-identity-unreadable"];
10
+ /** A kernel unique id: a decimal unsigned 64-bit integer as text (the bound itself is checked after parsing). */
11
+ const kernelId = z.string().regex(/^(0|[1-9][0-9]{0,19})$/);
12
+ const count = z.int().min(0);
13
+ const version32 = z.int().min(0).max(0xffffffff);
14
+ export const darwinProcessIdentitySchema = z.object({
15
+ pid: z.int().min(1),
16
+ ppid: count.nullable(),
17
+ uid: count.nullable(),
18
+ birthMs: z.number().positive().nullable(),
19
+ uniqueId: kernelId.nullable(),
20
+ parentUniqueId: kernelId.nullable(),
21
+ traced: z.boolean().nullable(),
22
+ executable: z.string().max(4096).regex(/^[^\x00-\x1f\x7f]*$/).nullable(),
23
+ originalParentVersion: version32.nullable(),
24
+ pidVersion: version32.exactOptional(),
25
+ });
26
+ /** Top level only: process rows are read one by one, so a bad row gets its own reason. */
27
+ export const darwinNativeCensusSchema = z.object({
28
+ schema: z.literal(1),
29
+ bootId: z.string().regex(/^[a-fA-F0-9-]{36}$/).nullable(),
30
+ collectorPid: z.int().min(2),
31
+ complete: z.boolean(),
32
+ processes: z.array(z.unknown()).max(100_000),
33
+ errors: z.array(z.enum(NATIVE_CENSUS_ERRORS)),
34
+ });
35
+ /** A kernel id within 64 bits (the schema checks the digits). */
36
+ export const kernelIdInRange = (id) => BigInt(id) <= 0xffffffffffffffffn;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * A focused observation request (observations.ts `parseObservationCases`): the file a builder writes to ask for one to
3
+ * four focused test runs, each naming a criterion, base or head, one test file and one test name.
4
+ *
5
+ * Toolroll defines this request, so it is strict: an unknown key, at the top or in an observation, is refused, as it
6
+ * always was. It must say `version: 1`, as it always had to: a request without `version`, or with any other version, is
7
+ * refused. What JSON Schema can't say —
8
+ * a criterion the brief asked about, a normalized test path, a usable test name, one distinct observation per
9
+ * criterion — is checked after parsing.
10
+ */
11
+ import { z } from "zod";
12
+ import { type ContractResult } from "./contract.js";
13
+ export declare const observationCaseSchema: z.ZodObject<{
14
+ criterion: z.ZodString;
15
+ at: z.ZodEnum<{
16
+ base: "base";
17
+ head: "head";
18
+ }>;
19
+ testPath: z.ZodString;
20
+ testName: z.ZodString;
21
+ }, z.core.$strict>;
22
+ export declare const observationRequestSchema: z.ZodObject<{
23
+ version: z.ZodLiteral<1>;
24
+ observations: z.ZodArray<z.ZodObject<{
25
+ criterion: z.ZodString;
26
+ at: z.ZodEnum<{
27
+ base: "base";
28
+ head: "head";
29
+ }>;
30
+ testPath: z.ZodString;
31
+ testName: z.ZodString;
32
+ }, z.core.$strict>>;
33
+ }, z.core.$strict>;
34
+ export type ObservationCase = z.infer<typeof observationCaseSchema>;
35
+ export type ObservationRequest = z.infer<typeof observationRequestSchema>;
36
+ /** A parsed request (JSON already read). Only version 1 is read; there is no earlier version to upgrade. */
37
+ export declare function readObservationRequest(input: unknown): ContractResult<ObservationRequest>;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * A focused observation request (observations.ts `parseObservationCases`): the file a builder writes to ask for one to
3
+ * four focused test runs, each naming a criterion, base or head, one test file and one test name.
4
+ *
5
+ * Toolroll defines this request, so it is strict: an unknown key, at the top or in an observation, is refused, as it
6
+ * always was. It must say `version: 1`, as it always had to: a request without `version`, or with any other version, is
7
+ * refused. What JSON Schema can't say —
8
+ * a criterion the brief asked about, a normalized test path, a usable test name, one distinct observation per
9
+ * criterion — is checked after parsing.
10
+ */
11
+ import { z } from "zod";
12
+ import { readVersioned, versioned } from "./contract.js";
13
+ export const observationCaseSchema = z.strictObject({
14
+ criterion: z.string(),
15
+ at: z.enum(["base", "head"]),
16
+ testPath: z.string(),
17
+ testName: z.string(),
18
+ });
19
+ export const observationRequestSchema = versioned(1, {
20
+ observations: z.array(observationCaseSchema).min(1).max(4),
21
+ });
22
+ /** A parsed request (JSON already read). Only version 1 is read; there is no earlier version to upgrade. */
23
+ export function readObservationRequest(input) {
24
+ return readVersioned(observationRequestSchema, input);
25
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Which harness runs, on which model (provider.ts `AgentSpec`): a provider id and a model id, null for the harness's
3
+ * default. One schema for the pair; `ProviderId` and `AgentSpec` are derived from it. Model ids cross providers
4
+ * ("anthropic/claude-sonnet-4.5", "gpt-5-codex", "opus"): bounded and printable, never leading with a dash (argv
5
+ * safety), and no TOML-hostile characters. That OpenRouter and Gemini need an explicit model runs after parsing.
6
+ *
7
+ * A refusal names its field and never repeats the value it was given: a provider or model field is where a pasted
8
+ * credential would land, and a refusal is shown, logged and sent on.
9
+ */
10
+ import { z } from "zod";
11
+ export declare const PROVIDER_ID_VALUES: readonly ["claude", "codex", "openrouter", "gemini"];
12
+ export declare const providerIdSchema: z.ZodEnum<{
13
+ claude: "claude";
14
+ codex: "codex";
15
+ gemini: "gemini";
16
+ openrouter: "openrouter";
17
+ }>;
18
+ export declare const MODEL_ID: RegExp;
19
+ export declare const modelIdSchema: z.ZodString;
20
+ export declare const agentSpecSchema: z.ZodObject<{
21
+ provider: z.ZodEnum<{
22
+ claude: "claude";
23
+ codex: "codex";
24
+ gemini: "gemini";
25
+ openrouter: "openrouter";
26
+ }>;
27
+ model: z.ZodNullable<z.ZodString>;
28
+ }, z.core.$strip>;
29
+ export type ProviderId = z.infer<typeof providerIdSchema>;
30
+ export type AgentSpec = z.infer<typeof agentSpecSchema>;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Which harness runs, on which model (provider.ts `AgentSpec`): a provider id and a model id, null for the harness's
3
+ * default. One schema for the pair; `ProviderId` and `AgentSpec` are derived from it. Model ids cross providers
4
+ * ("anthropic/claude-sonnet-4.5", "gpt-5-codex", "opus"): bounded and printable, never leading with a dash (argv
5
+ * safety), and no TOML-hostile characters. That OpenRouter and Gemini need an explicit model runs after parsing.
6
+ *
7
+ * A refusal names its field and never repeats the value it was given: a provider or model field is where a pasted
8
+ * credential would land, and a refusal is shown, logged and sent on.
9
+ */
10
+ import { z } from "zod";
11
+ export const PROVIDER_ID_VALUES = ["claude", "codex", "openrouter", "gemini"];
12
+ export const providerIdSchema = z.enum(PROVIDER_ID_VALUES);
13
+ export const MODEL_ID = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/;
14
+ export const modelIdSchema = z.string().regex(MODEL_ID);
15
+ export const agentSpecSchema = z.object({ provider: providerIdSchema, model: modelIdSchema.nullable() });
@@ -0,0 +1,166 @@
1
+ /**
2
+ * A recipe document (recipes.ts): reusable work a person imports, exports, previews and saves. One schema for both
3
+ * versions, a discriminated union on `version` — version 1 is fixed work, version 2 adds questions whose answers fill
4
+ * `{{key}}` placeholders. Recipes are strict and always were: every key is required, and an unknown one (an approval,
5
+ * provider or credential setting smuggled in) is refused by name.
6
+ *
7
+ * A success check is the scope's own saved criterion (src/contracts/scope.ts), strict about its keys. The reader fills
8
+ * an absent or empty `how` with null before parsing, as the scope reader always has. What JSON Schema can't say runs
9
+ * in recipes.ts after parsing, each refusal naming its field: the 32 KB size, text and placeholder rules, schedules,
10
+ * the budget/schedule/deliverable/planning combinations, question keys and the scope reader's byte, control-character
11
+ * and duplicate checks. Saved recipes and previews keep their exact bytes, so their digests still match.
12
+ */
13
+ import { z } from "zod";
14
+ import { type ContractResult } from "./contract.js";
15
+ export declare const RECIPE_FORMAT = "standing-orders-recipe";
16
+ /** Numbers the recipe rules use, in plain code after parsing (bytes of the whole document, characters of a field). */
17
+ export declare const RECIPE_LIMITS: {
18
+ readonly documentBytes: 32768;
19
+ readonly description: 400;
20
+ readonly inputs: 8;
21
+ readonly label: 80;
22
+ readonly answer: 500;
23
+ };
24
+ /** A question a version 2 recipe asks: its key, its label and an optional default answer (null for none). */
25
+ export declare const recipeInputSchema: z.ZodObject<{
26
+ key: z.ZodString;
27
+ label: z.ZodString;
28
+ defaultValue: z.ZodNullable<z.ZodString>;
29
+ }, z.core.$strict>;
30
+ export declare const recipeV1Schema: z.ZodObject<{
31
+ version: z.ZodLiteral<1>;
32
+ format: z.ZodLiteral<"standing-orders-recipe">;
33
+ name: z.ZodString;
34
+ description: z.ZodString;
35
+ goal: z.ZodString;
36
+ outOfScope: z.ZodNullable<z.ZodString>;
37
+ touches: z.ZodArray<z.ZodString>;
38
+ acceptance: z.ZodArray<z.ZodLazy<z.ZodObject<{
39
+ id: z.ZodString;
40
+ statement: z.ZodString;
41
+ evidence: z.ZodArray<z.ZodEnum<{
42
+ "changed-path": "changed-path";
43
+ check: "check";
44
+ "manual-review": "manual-review";
45
+ screenshot: "screenshot";
46
+ }>>;
47
+ how: z.ZodNullable<z.ZodString>;
48
+ }, z.core.$strict>>>;
49
+ planning: z.ZodEnum<{
50
+ auto: "auto";
51
+ required: "required";
52
+ skip: "skip";
53
+ }>;
54
+ deliverable: z.ZodEnum<{
55
+ branch: "branch";
56
+ report: "report";
57
+ }>;
58
+ schedule: z.ZodNullable<z.ZodString>;
59
+ costCeilingUsd: z.ZodNullable<z.ZodNumber>;
60
+ }, z.core.$strict>;
61
+ export declare const recipeV2Schema: z.ZodObject<{
62
+ version: z.ZodLiteral<2>;
63
+ format: z.ZodLiteral<"standing-orders-recipe">;
64
+ name: z.ZodString;
65
+ description: z.ZodString;
66
+ goal: z.ZodString;
67
+ outOfScope: z.ZodNullable<z.ZodString>;
68
+ touches: z.ZodArray<z.ZodString>;
69
+ acceptance: z.ZodArray<z.ZodLazy<z.ZodObject<{
70
+ id: z.ZodString;
71
+ statement: z.ZodString;
72
+ evidence: z.ZodArray<z.ZodEnum<{
73
+ "changed-path": "changed-path";
74
+ check: "check";
75
+ "manual-review": "manual-review";
76
+ screenshot: "screenshot";
77
+ }>>;
78
+ how: z.ZodNullable<z.ZodString>;
79
+ }, z.core.$strict>>>;
80
+ planning: z.ZodEnum<{
81
+ auto: "auto";
82
+ required: "required";
83
+ skip: "skip";
84
+ }>;
85
+ deliverable: z.ZodEnum<{
86
+ branch: "branch";
87
+ report: "report";
88
+ }>;
89
+ schedule: z.ZodNullable<z.ZodString>;
90
+ costCeilingUsd: z.ZodNullable<z.ZodNumber>;
91
+ inputs: z.ZodArray<z.ZodObject<{
92
+ key: z.ZodString;
93
+ label: z.ZodString;
94
+ defaultValue: z.ZodNullable<z.ZodString>;
95
+ }, z.core.$strict>>;
96
+ }, z.core.$strict>;
97
+ export declare const recipeDocumentSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
98
+ version: z.ZodLiteral<1>;
99
+ format: z.ZodLiteral<"standing-orders-recipe">;
100
+ name: z.ZodString;
101
+ description: z.ZodString;
102
+ goal: z.ZodString;
103
+ outOfScope: z.ZodNullable<z.ZodString>;
104
+ touches: z.ZodArray<z.ZodString>;
105
+ acceptance: z.ZodArray<z.ZodLazy<z.ZodObject<{
106
+ id: z.ZodString;
107
+ statement: z.ZodString;
108
+ evidence: z.ZodArray<z.ZodEnum<{
109
+ "changed-path": "changed-path";
110
+ check: "check";
111
+ "manual-review": "manual-review";
112
+ screenshot: "screenshot";
113
+ }>>;
114
+ how: z.ZodNullable<z.ZodString>;
115
+ }, z.core.$strict>>>;
116
+ planning: z.ZodEnum<{
117
+ auto: "auto";
118
+ required: "required";
119
+ skip: "skip";
120
+ }>;
121
+ deliverable: z.ZodEnum<{
122
+ branch: "branch";
123
+ report: "report";
124
+ }>;
125
+ schedule: z.ZodNullable<z.ZodString>;
126
+ costCeilingUsd: z.ZodNullable<z.ZodNumber>;
127
+ }, z.core.$strict>, z.ZodObject<{
128
+ version: z.ZodLiteral<2>;
129
+ format: z.ZodLiteral<"standing-orders-recipe">;
130
+ name: z.ZodString;
131
+ description: z.ZodString;
132
+ goal: z.ZodString;
133
+ outOfScope: z.ZodNullable<z.ZodString>;
134
+ touches: z.ZodArray<z.ZodString>;
135
+ acceptance: z.ZodArray<z.ZodLazy<z.ZodObject<{
136
+ id: z.ZodString;
137
+ statement: z.ZodString;
138
+ evidence: z.ZodArray<z.ZodEnum<{
139
+ "changed-path": "changed-path";
140
+ check: "check";
141
+ "manual-review": "manual-review";
142
+ screenshot: "screenshot";
143
+ }>>;
144
+ how: z.ZodNullable<z.ZodString>;
145
+ }, z.core.$strict>>>;
146
+ planning: z.ZodEnum<{
147
+ auto: "auto";
148
+ required: "required";
149
+ skip: "skip";
150
+ }>;
151
+ deliverable: z.ZodEnum<{
152
+ branch: "branch";
153
+ report: "report";
154
+ }>;
155
+ schedule: z.ZodNullable<z.ZodString>;
156
+ costCeilingUsd: z.ZodNullable<z.ZodNumber>;
157
+ inputs: z.ZodArray<z.ZodObject<{
158
+ key: z.ZodString;
159
+ label: z.ZodString;
160
+ defaultValue: z.ZodNullable<z.ZodString>;
161
+ }, z.core.$strict>>;
162
+ }, z.core.$strict>], "version">;
163
+ export type RecipeDocument = z.infer<typeof recipeDocumentSchema>;
164
+ export type RecipeInput = z.infer<typeof recipeInputSchema>;
165
+ /** Read a recipe's structure (format and version already checked): every refusal is a path-named issue. */
166
+ export declare function readRecipeDocument(input: Record<string, unknown>): ContractResult<RecipeDocument>;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * A recipe document (recipes.ts): reusable work a person imports, exports, previews and saves. One schema for both
3
+ * versions, a discriminated union on `version` — version 1 is fixed work, version 2 adds questions whose answers fill
4
+ * `{{key}}` placeholders. Recipes are strict and always were: every key is required, and an unknown one (an approval,
5
+ * provider or credential setting smuggled in) is refused by name.
6
+ *
7
+ * A success check is the scope's own saved criterion (src/contracts/scope.ts), strict about its keys. The reader fills
8
+ * an absent or empty `how` with null before parsing, as the scope reader always has. What JSON Schema can't say runs
9
+ * in recipes.ts after parsing, each refusal naming its field: the 32 KB size, text and placeholder rules, schedules,
10
+ * the budget/schedule/deliverable/planning combinations, question keys and the scope reader's byte, control-character
11
+ * and duplicate checks. Saved recipes and previews keep their exact bytes, so their digests still match.
12
+ */
13
+ import { z } from "zod";
14
+ import { parseContract, versioned } from "./contract.js";
15
+ import { savedCriterionSchema } from "./scope.js";
16
+ export const RECIPE_FORMAT = "standing-orders-recipe";
17
+ /** Numbers the recipe rules use, in plain code after parsing (bytes of the whole document, characters of a field). */
18
+ export const RECIPE_LIMITS = { documentBytes: 32_768, description: 400, inputs: 8, label: 80, answer: 500 };
19
+ /** A question a version 2 recipe asks: its key, its label and an optional default answer (null for none). */
20
+ export const recipeInputSchema = z.strictObject({
21
+ key: z.string(),
22
+ label: z.string(),
23
+ defaultValue: z.string().nullable(),
24
+ });
25
+ const recipeFields = {
26
+ format: z.literal(RECIPE_FORMAT),
27
+ name: z.string(),
28
+ description: z.string(),
29
+ goal: z.string(),
30
+ outOfScope: z.string().nullable(),
31
+ touches: z.array(z.string()),
32
+ acceptance: z.array(savedCriterionSchema),
33
+ planning: z.enum(["auto", "required", "skip"]),
34
+ deliverable: z.enum(["branch", "report"]),
35
+ schedule: z.string().nullable(),
36
+ costCeilingUsd: z.number().positive().nullable(),
37
+ };
38
+ export const recipeV1Schema = versioned(1, recipeFields);
39
+ export const recipeV2Schema = versioned(2, { ...recipeFields, inputs: z.array(recipeInputSchema).min(1).max(RECIPE_LIMITS.inputs) });
40
+ export const recipeDocumentSchema = z.discriminatedUnion("version", [recipeV1Schema, recipeV2Schema]);
41
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
42
+ /** A success check as the scope reader reads it: an absent or empty `how` is null; every other key, known or not, as given. */
43
+ function checkBody(entry) {
44
+ if (!isRecord(entry) || (entry["how"] !== undefined && entry["how"] !== ""))
45
+ return entry;
46
+ return { ...entry, how: null };
47
+ }
48
+ /** Read a recipe's structure (format and version already checked): every refusal is a path-named issue. */
49
+ export function readRecipeDocument(input) {
50
+ const body = Array.isArray(input["acceptance"]) ? { ...input, acceptance: input["acceptance"].map(checkBody) } : input;
51
+ return input["version"] === 2 ? parseContract(recipeV2Schema, body) : parseContract(recipeV1Schema, body);
52
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Retention settings (retention.ts): which kinds of data have a period, and how long each is kept — a whole number of
3
+ * days from 1 to 3650, or null for forever. One schema for the kind, the period and the full set of periods; the
4
+ * types retention.ts exports are derived from them.
5
+ *
6
+ * `retention_setting` rows are read by the store (`retentionChosen`, item 18's to move onto `retentionRowSchema`): a
7
+ * row's kind and days as SQLite's CHECK constraint keeps them, unknown columns ignored. Text a person types ("90d",
8
+ * "1y", "forever") is turned into days in plain code (retention.ts `parsePeriod`) and then checked by this schema.
9
+ */
10
+ import { z } from "zod";
11
+ export declare const RETENTION_KIND_VALUES: readonly ["evidence", "checkouts", "chat", "notifications"];
12
+ export declare const retentionKindSchema: z.ZodEnum<{
13
+ chat: "chat";
14
+ checkouts: "checkouts";
15
+ evidence: "evidence";
16
+ notifications: "notifications";
17
+ }>;
18
+ export declare const RETENTION_DAYS: {
19
+ readonly min: 1;
20
+ readonly max: 3650;
21
+ };
22
+ /** A period: whole days from 1 to 3650, or null for forever. */
23
+ export declare const retentionDaysSchema: z.ZodNullable<z.ZodInt>;
24
+ /** Every kind's period, as the sweep and the settings page read them. */
25
+ export declare const retentionPeriodsSchema: z.ZodObject<{
26
+ evidence: z.ZodNullable<z.ZodInt>;
27
+ checkouts: z.ZodNullable<z.ZodInt>;
28
+ chat: z.ZodNullable<z.ZodInt>;
29
+ notifications: z.ZodNullable<z.ZodInt>;
30
+ }, z.core.$strip>;
31
+ /** A saved `retention_setting` row: only a kind someone chose has one. */
32
+ export declare const retentionRowSchema: z.ZodObject<{
33
+ kind: z.ZodEnum<{
34
+ chat: "chat";
35
+ checkouts: "checkouts";
36
+ evidence: "evidence";
37
+ notifications: "notifications";
38
+ }>;
39
+ days: z.ZodNullable<z.ZodInt>;
40
+ }, z.core.$strip>;
41
+ export type RetentionKind = z.infer<typeof retentionKindSchema>;
42
+ export type RetentionPeriods = z.infer<typeof retentionPeriodsSchema>;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Retention settings (retention.ts): which kinds of data have a period, and how long each is kept — a whole number of
3
+ * days from 1 to 3650, or null for forever. One schema for the kind, the period and the full set of periods; the
4
+ * types retention.ts exports are derived from them.
5
+ *
6
+ * `retention_setting` rows are read by the store (`retentionChosen`, item 18's to move onto `retentionRowSchema`): a
7
+ * row's kind and days as SQLite's CHECK constraint keeps them, unknown columns ignored. Text a person types ("90d",
8
+ * "1y", "forever") is turned into days in plain code (retention.ts `parsePeriod`) and then checked by this schema.
9
+ */
10
+ import { z } from "zod";
11
+ export const RETENTION_KIND_VALUES = ["evidence", "checkouts", "chat", "notifications"];
12
+ export const retentionKindSchema = z.enum(RETENTION_KIND_VALUES);
13
+ export const RETENTION_DAYS = { min: 1, max: 3650 };
14
+ /** A period: whole days from 1 to 3650, or null for forever. */
15
+ export const retentionDaysSchema = z.int().min(RETENTION_DAYS.min).max(RETENTION_DAYS.max).nullable();
16
+ /** Every kind's period, as the sweep and the settings page read them. */
17
+ export const retentionPeriodsSchema = z.object({
18
+ evidence: retentionDaysSchema,
19
+ checkouts: retentionDaysSchema,
20
+ chat: retentionDaysSchema,
21
+ notifications: retentionDaysSchema,
22
+ });
23
+ /** A saved `retention_setting` row: only a kind someone chose has one. */
24
+ export const retentionRowSchema = z.object({ kind: retentionKindSchema, days: retentionDaysSchema });
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The checkout cleanup setting (storage.ts, Settings → Storage): when a finished task's clean checkout is removed.
3
+ * One schema for the four values `checkout_cleanup_setting.cleanup` may hold, and `CheckoutCleanup` derived from it.
4
+ * The words a person types ("week", "off", "2 days") become a value in plain code (storage.ts `parseCleanup`) and
5
+ * are then checked by this schema. The saved row is read by the store (item 18's to move onto `checkoutCleanupRowSchema`).
6
+ */
7
+ import { z } from "zod";
8
+ export declare const checkoutCleanupSchema: z.ZodEnum<{
9
+ "2d": "2d";
10
+ "7d": "7d";
11
+ finished: "finished";
12
+ never: "never";
13
+ }>;
14
+ /** A saved `checkout_cleanup_setting` row (at most one; none means the default), unknown columns ignored. */
15
+ export declare const checkoutCleanupRowSchema: z.ZodObject<{
16
+ cleanup: z.ZodEnum<{
17
+ "2d": "2d";
18
+ "7d": "7d";
19
+ finished: "finished";
20
+ never: "never";
21
+ }>;
22
+ }, z.core.$strip>;
23
+ export type CheckoutCleanup = z.infer<typeof checkoutCleanupSchema>;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The checkout cleanup setting (storage.ts, Settings → Storage): when a finished task's clean checkout is removed.
3
+ * One schema for the four values `checkout_cleanup_setting.cleanup` may hold, and `CheckoutCleanup` derived from it.
4
+ * The words a person types ("week", "off", "2 days") become a value in plain code (storage.ts `parseCleanup`) and
5
+ * are then checked by this schema. The saved row is read by the store (item 18's to move onto `checkoutCleanupRowSchema`).
6
+ */
7
+ import { z } from "zod";
8
+ export const checkoutCleanupSchema = z.enum(["finished", "2d", "7d", "never"]);
9
+ /** A saved `checkout_cleanup_setting` row (at most one; none means the default), unknown columns ignored. */
10
+ export const checkoutCleanupRowSchema = z.object({ cleanup: checkoutCleanupSchema });