@intentic/sandbox-contract 1.224.0 → 1.225.0

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 (107) hide show
  1. package/README.md +14 -14
  2. package/dist/agent-catalog.js +3 -3
  3. package/dist/agent-catalog.js.map +1 -1
  4. package/dist/chores/chores.js +42 -42
  5. package/dist/chores/chores.js.map +1 -1
  6. package/dist/chores/extension-update.js +2 -2
  7. package/dist/chores/extension-update.js.map +1 -1
  8. package/dist/chores/fix-deps.js +1 -1
  9. package/dist/chores/fix-deps.js.map +1 -1
  10. package/dist/chores/probes.js +1 -1
  11. package/dist/chores/probes.js.map +1 -1
  12. package/dist/chores/verdict.js +2 -2
  13. package/dist/chores/verdict.js.map +1 -1
  14. package/dist/contracts/capabilities.contract.d.ts +41 -0
  15. package/dist/contracts/capabilities.contract.d.ts.map +1 -1
  16. package/dist/contracts/exit.contract.d.ts +80 -0
  17. package/dist/contracts/exit.contract.d.ts.map +1 -0
  18. package/dist/contracts/exit.contract.js +13 -0
  19. package/dist/contracts/exit.contract.js.map +1 -0
  20. package/dist/contracts/settings.contract.d.ts +12 -0
  21. package/dist/contracts/settings.contract.d.ts.map +1 -1
  22. package/dist/events.d.ts +4 -4
  23. package/dist/events.d.ts.map +1 -1
  24. package/dist/events.js +5 -5
  25. package/dist/events.js.map +1 -1
  26. package/dist/fast-tier.d.ts +9 -0
  27. package/dist/fast-tier.d.ts.map +1 -0
  28. package/dist/fast-tier.js +19 -0
  29. package/dist/fast-tier.js.map +1 -0
  30. package/dist/history-state.js +3 -3
  31. package/dist/history-state.js.map +1 -1
  32. package/dist/index.d.ts +203 -68
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +5 -0
  35. package/dist/index.js.map +1 -1
  36. package/dist/model-order.d.ts +1 -0
  37. package/dist/model-order.d.ts.map +1 -1
  38. package/dist/model-order.js +5 -0
  39. package/dist/model-order.js.map +1 -1
  40. package/dist/output-fields.d.ts.map +1 -1
  41. package/dist/output-fields.js +2 -2
  42. package/dist/output-fields.js.map +1 -1
  43. package/dist/prompt-complexity.d.ts +19 -0
  44. package/dist/prompt-complexity.d.ts.map +1 -0
  45. package/dist/prompt-complexity.js +83 -0
  46. package/dist/prompt-complexity.js.map +1 -0
  47. package/dist/publish-drafts.js +2 -2
  48. package/dist/publish-drafts.js.map +1 -1
  49. package/dist/schemas.d.ts +214 -0
  50. package/dist/schemas.d.ts.map +1 -1
  51. package/dist/schemas.js +113 -2
  52. package/dist/schemas.js.map +1 -1
  53. package/dist/workflow-faults.js +3 -3
  54. package/dist/workflow-faults.js.map +1 -1
  55. package/dist/workspace-state.d.ts +20 -20
  56. package/dist/workspace-state.d.ts.map +1 -1
  57. package/dist/workspace-state.js +20 -20
  58. package/dist/workspace-state.js.map +1 -1
  59. package/package.json +5 -5
  60. package/src/agent-catalog.test.ts +25 -25
  61. package/src/agent-catalog.ts +3 -3
  62. package/src/agent-run-model.test.ts +3 -3
  63. package/src/capability-ledger.test.ts +13 -13
  64. package/src/chores/chores.test.ts +1 -1
  65. package/src/chores/chores.ts +42 -42
  66. package/src/chores/digest.test.ts +1 -1
  67. package/src/chores/extension-update.ts +2 -2
  68. package/src/chores/fix-deps.ts +1 -1
  69. package/src/chores/probes.test.ts +6 -6
  70. package/src/chores/probes.ts +1 -1
  71. package/src/chores/stack.test.ts +3 -3
  72. package/src/chores/verdict.test.ts +20 -20
  73. package/src/chores/verdict.ts +2 -2
  74. package/src/contract-lock.test.ts +1 -1
  75. package/src/contracts/exit.contract.ts +42 -0
  76. package/src/contracts/providers.contract.ts +1 -1
  77. package/src/conversation-ids.ts +1 -1
  78. package/src/events.test.ts +3 -3
  79. package/src/events.ts +6 -6
  80. package/src/fast-tier.test.ts +88 -0
  81. package/src/fast-tier.ts +72 -0
  82. package/src/history-state.ts +3 -3
  83. package/src/hostnames.test.ts +1 -1
  84. package/src/index.ts +5 -0
  85. package/src/model-order.test.ts +11 -11
  86. package/src/model-order.ts +22 -0
  87. package/src/output-fields.ts +2 -2
  88. package/src/path-refs.test.ts +4 -4
  89. package/src/prompt-complexity.test.ts +160 -0
  90. package/src/prompt-complexity.ts +271 -0
  91. package/src/publish-drafts.ts +2 -2
  92. package/src/quick-model.test.ts +11 -11
  93. package/src/routes.test.ts +11 -5
  94. package/src/runtime-state.test.ts +1 -1
  95. package/src/schemas.test.ts +8 -8
  96. package/src/schemas.ts +311 -6
  97. package/src/search-globs.test.ts +2 -2
  98. package/src/share-paths.test.ts +1 -1
  99. package/src/title.test.ts +9 -9
  100. package/src/title.ts +1 -1
  101. package/src/tunnel-ids.test.ts +3 -3
  102. package/src/versions.test.ts +3 -3
  103. package/src/versions.ts +1 -1
  104. package/src/workflow-faults.test.ts +6 -6
  105. package/src/workflow-faults.ts +3 -3
  106. package/src/workspace-state.test.ts +32 -32
  107. package/src/workspace-state.ts +20 -20
@@ -25,15 +25,15 @@ test("isBehind stays quiet whenever it cannot be sure", () => {
25
25
  });
26
26
 
27
27
  /* A version with a segment that will not parse is read by its numeric prefix, and the failure is one-directional:
28
- * it can withhold a nag, never invent one. Both halves are pinned because only the second is a safety property
28
+ * it can withhold a nag, never invent one. Both halves are pinned because only the second is a safety property:
29
29
  * the first is just the prefix doing its job. */
30
30
  test("a version that isn't dotted-numeric can only ever withhold the nudge", () => {
31
31
  expect(isBehind("1.2.0-rc.1", "1.183.0")).toBe(true); // the prefix already decides it: 2 is behind 183
32
32
  expect(isBehind("1.2.0-rc.1", "1.2.0")).toBe(false); // the unparseable segment is where they differ — silence
33
33
  });
34
34
 
35
- // The version every agent shipped before the release stamp existed. It is genuinely behind it predates every
36
- // release that has one and it must read that way, because those are the installs this whole signal is for.
35
+ // The version every agent shipped before the release stamp existed. It is genuinely behind: it predates every
36
+ // release that has one, and it must read that way, because those are the installs this whole signal is for.
37
37
  test("isBehind flags the hand-written version agents used to carry", () => {
38
38
  expect(isBehind("0.1.0", "1.183.0")).toBe(true);
39
39
  });
package/src/versions.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  * that one" is one question with one answer, and it lives here because both ends ask it: the daemon compares its
4
4
  * own build against the latest published release, and the browser compares a computer's agent against the same.
5
5
  *
6
- * Shared rather than copied because the two copies would not disagree until the day it mattered 1.9.0 against
6
+ * Shared rather than copied because the two copies would not disagree until the day it mattered: 1.9.0 against
7
7
  * 1.10.0 is where a hand-rolled comparator goes wrong, and it goes wrong by reporting "up to date". */
8
8
 
9
9
  // Release versions are plain dotted numerics (semantic-release picks them), so there is no semver dependency to
@@ -3,7 +3,7 @@ import { type Workflow, WorkflowSchema, type WorkflowStep } from "./schemas.js";
3
3
  import { workflowFaults, workflowRunFaults } from "./workflow-faults.js";
4
4
 
5
5
  /* The GATE rules. The graph rules beside them are exercised by the scheduler's own integration tests, which
6
- * have a real run to check them against; these have none to check, which is the point every fault here is one
6
+ * have a real run to check them against; these have none to check, which is the point: every fault here is one
7
7
  * the workflow has to be refused for BEFORE a run, because the failure it prevents costs a full fan-out of
8
8
  * sessions and then reports nothing anybody can act on.
9
9
  */
@@ -78,7 +78,7 @@ test("a gate on a field the step does not declare is refused", () => {
78
78
  test("a gate on a list field is refused", () => {
79
79
  const steps = [judge({ output: { kind: "json", fields: [{ name: "release", type: "string[]", description: "the verdicts", required: true }] } })];
80
80
  const faults = workflowFaults(gated({ steps }));
81
- expect(faults).toEqual([`The gate reads "release", which is a list a release decision has to be one value.`]);
81
+ expect(faults).toEqual([`The gate reads "release", which is a list: a release decision has to be one value.`]);
82
82
  });
83
83
 
84
84
  // The expensive one to discover at run time: it passes every save, then blocks a release on the one commit
@@ -86,10 +86,10 @@ test("a gate on a list field is refused", () => {
86
86
  test("a gate on an optional field is refused", () => {
87
87
  const steps = [judge({ output: { kind: "json", fields: [{ name: "release", type: "string", description: "pass | fail", required: false }] } })];
88
88
  const faults = workflowFaults(gated({ steps }));
89
- expect(faults).toEqual([`The gate reads "release", which "Judge" declares optional it has to be required.`]);
89
+ expect(faults).toEqual([`The gate reads "release", which "Judge" declares optional, it has to be required.`]);
90
90
  });
91
91
 
92
- /* THE RUN-TIME RULE, kept apart from every rule above it because it is not about the graph and the graph is
92
+ /* THE RUN-TIME RULE, kept apart from every rule above it because it is not about the graph, and the graph is
93
93
  * what gets saved. A design whose steps take their goal and instruction from the request is the ordinary shape
94
94
  * and must save cleanly; it is only unrunnable on the particular run that brought no request.
95
95
  */
@@ -102,7 +102,7 @@ test("a design whose steps inherit is a perfectly good design", () => {
102
102
  });
103
103
 
104
104
  /* Refused at the door rather than discovered by the first step, because this is the one combination with
105
- * nothing to tell the model at all and by the time a step found out, the run has already opened a session per
105
+ * nothing to tell the model at all, and by the time a step found out, the run has already opened a session per
106
106
  * root and started paying for them.
107
107
  */
108
108
  test("running an inheriting design with no request is refused", () => {
@@ -120,7 +120,7 @@ test("a step that declares only one of the two still needs a request", () => {
120
120
  expect(workflowRunFaults(gated({ steps: [judge({ prompt: undefined })] }), undefined)).toHaveLength(1);
121
121
  });
122
122
 
123
- // A design that says everything itself is startable from anywhere, with no composer behind it which is what
123
+ // A design that says everything itself is startable from anywhere, with no composer behind it, which is what
124
124
  // keeps the gate's webhook and the workflows page working for the designs written that way.
125
125
  test("a design that declares everything runs with no request at all", () => {
126
126
  expect(workflowRunFaults(gated(), undefined)).toEqual([]);
@@ -38,7 +38,7 @@ const stepFaults = (step: WorkflowStep, ids: ReadonlySet<string>): string[] => {
38
38
  if (step.handoff === "continue" && !continuesOneSession(step)) {
39
39
  faults.push(
40
40
  step.needs.length === 0
41
- ? `"${step.title}" continues a session but starts the run there is nothing to continue.`
41
+ ? `"${step.title}" continues a session but starts the run: there is nothing to continue.`
42
42
  : `"${step.title}" continues a session but waits for ${step.needs.length} steps; it can only continue one.`,
43
43
  );
44
44
  }
@@ -136,7 +136,7 @@ const gateFaults = (workflow: Pick<Workflow, "steps" | "gate">): string[] => {
136
136
  return [`The gate reads "${gate.field}", which "${step.title}" does not declare.`];
137
137
  }
138
138
  if (field.type === "string[]") {
139
- return [`The gate reads "${gate.field}", which is a list a release decision has to be one value.`];
139
+ return [`The gate reads "${gate.field}", which is a list: a release decision has to be one value.`];
140
140
  }
141
141
  // The schema refuses this on save (pass is min(1)), but the designer edits drafts the schema never sees,
142
142
  // and an empty allowlist is a gate no run could ever answer "pass", which deserves a sentence, not a save error.
@@ -145,7 +145,7 @@ const gateFaults = (workflow: Pick<Workflow, "steps" | "gate">): string[] => {
145
145
  }
146
146
  // A field the step may legally omit is a gate that answers `blocked` whenever it does, which is a release
147
147
  // stuck on a technicality rather than on the product.
148
- return field.required ? [] : [`The gate reads "${gate.field}", which "${step.title}" declares optional it has to be required.`];
148
+ return field.required ? [] : [`The gate reads "${gate.field}", which "${step.title}" declares optional, it has to be required.`];
149
149
  };
150
150
 
151
151
  /* Why the graph is not runnable, as a list of sentences. Empty ⇒ it is. Shared by the save route (which
@@ -21,7 +21,7 @@ import {
21
21
  // What the automations extension declares in its manifest, and what the memory extension WOULD declare if the
22
22
  // watcher reported its files. Literals rather than the real manifests: an extension package importing this one is
23
23
  // the dependency direction, so reaching back for them here would invert it. The real manifests are checked
24
- // against this rule where they are loaded web's fileBindings.test.ts and the daemon's file-bindings.test.ts.
24
+ // against this rule where they are loaded: web's fileBindings.test.ts and the daemon's file-bindings.test.ts.
25
25
  const AUTOMATIONS: readonly FileContribution[] = [
26
26
  { path: `${STATE_DIR}/config/automations.json`, invalidates: [`automations`] },
27
27
  { path: `${STATE_DIR}/records/approvals/`, invalidates: [`automation-approvals`] },
@@ -45,14 +45,14 @@ describe(`staleQueryKeys`, () => {
45
45
  });
46
46
 
47
47
  it(`matches a name family and a one-file-per-entry directory through one prefix each`, () => {
48
- // environment.Dockerfile, environment.custom.Dockerfile, environment.approved.Dockerfile one entry.
48
+ // environment.Dockerfile, environment.custom.Dockerfile, environment.approved.Dockerfile: one entry.
49
49
  expect(staleQueryKeys([`.intentic/config/environment.custom.Dockerfile`], [])).toEqual([`environment`]);
50
50
  expect(staleQueryKeys([`.intentic/config/drafts/post-1.json`], [])).toEqual([`drafts`]);
51
51
  });
52
52
 
53
53
  it(`refreshes the Drafts view when the AGENT writes a draft`, () => {
54
54
  // The regression this table was reorganized around: the drafts skill writes these files directly, so
55
- // there is no browser mutation to hang an invalidate on the watcher push is the only signal, and it
55
+ // there is no browser mutation to hang an invalidate on: the watcher push is the only signal, and it
56
56
  // used to be dropped on the floor.
57
57
  expect(staleQueryKeys([`.intentic/config/drafts/post-1.json`], [])).toEqual([`drafts`]);
58
58
  });
@@ -75,7 +75,7 @@ describe(`staleQueryKeys`, () => {
75
75
  });
76
76
 
77
77
  it(`dedupes keys across a batch that touches several manifests`, () => {
78
- // A capability add recomposes the overlay, so both entries claim `environment` one refetch, not two.
78
+ // A capability add recomposes the overlay, so both entries claim `environment`: one refetch, not two.
79
79
  expect(staleQueryKeys([`.intentic/config/capabilities.json`, `.intentic/config/environment.Dockerfile`], [])).toEqual([
80
80
  `capabilities`,
81
81
  `environment`,
@@ -129,7 +129,7 @@ describe(`isReportedManifest`, () => {
129
129
 
130
130
  it(`stays quiet about daemon-written state the owner cannot repair`, () => {
131
131
  // The regression this exists for: the workflow ledger's records predated a schema that gained a required
132
- // field, so every read reported the whole file ignored advice ("fix the file") addressed to nobody,
132
+ // field, so every read reported the whole file ignored: advice ("fix the file") addressed to nobody,
133
133
  // about sixty kilobytes of machine JSON, and refreshed by no write because the ledger feeds no query, so
134
134
  // it sat on the card until the daemon restarted. A ledger recovers on its own next write instead.
135
135
  expect(isReportedManifest(`.intentic/records/workflow-runs.json`)).toBe(false);
@@ -142,7 +142,7 @@ describe(`isReportedManifest`, () => {
142
142
  });
143
143
 
144
144
  it(`does not report a file outside the workspace`, () => {
145
- // What `relative` hands the daemon for the manifests it keeps under /history never nameable on screen.
145
+ // What `relative` hands the daemon for the manifests it keeps under /history: never nameable on screen.
146
146
  expect(isReportedManifest(`../../history/settings.json`)).toBe(false);
147
147
  });
148
148
  });
@@ -158,7 +158,7 @@ describe(`WORKSPACE_STATE_FILES`, () => {
158
158
  it(`states a reason for every entry that invalidates nothing`, () => {
159
159
  // An empty `invalidates` is a real answer (daemon machine state, a deliberately-polled surface, a path
160
160
  // whose query keys belong to an extension), but a SILENT one is indistinguishable from the omission this
161
- // table exists to prevent which is exactly how drafts went missing. Requiring the reason is what makes
161
+ // table exists to prevent, which is exactly how drafts went missing. Requiring the reason is what makes
162
162
  // the difference visible at review time.
163
163
  for (const file of WORKSPACE_STATE_FILES) {
164
164
  if (file.invalidates.length === 0) {
@@ -179,7 +179,7 @@ describe(`WORKSPACE_STATE_FILES`, () => {
179
179
  });
180
180
 
181
181
  it(`only nests under an entry that invalidates nothing, so one write can't be billed twice`, () => {
182
- // Entries may nest when a subtree needs a different portability class stateFileFor's longest match
182
+ // Entries may nest when a subtree needs a different portability class: stateFileFor's longest match
183
183
  // keeps that unambiguous. Invalidation has no longest-match rule: staleQueryKeys unions every
184
184
  // matching entry, so a nest under an entry that DOES invalidate would bill the outer view's queries for
185
185
  // a write that belongs to the inner one. Nesting is therefore only legal beneath an empty `invalidates`.
@@ -247,7 +247,7 @@ describe(`isReviewableLockedPath`, () => {
247
247
  it(`admits the locked entry the root repo tracks, and nothing else locked`, () => {
248
248
  expect(isReviewableLockedPath(`.intentic/config/capabilities.json`)).toBe(true);
249
249
  // Every other locked entry is a credential, an identity binding or private runtime state. None is
250
- // versioned, so none is reachable through a diff the carve-out cannot widen without the flag.
250
+ // versioned, so none is reachable through a diff: the carve-out cannot widen without the flag.
251
251
  expect(isReviewableLockedPath(`.intentic/identity/owner.json`)).toBe(false);
252
252
  expect(isReviewableLockedPath(`.intentic/identity/members.json`)).toBe(false);
253
253
  expect(isReviewableLockedPath(`.intentic/secrets/ci.json`)).toBe(false);
@@ -257,7 +257,7 @@ describe(`isReviewableLockedPath`, () => {
257
257
  expect(isReviewableLockedPath(`.git/config`)).toBe(false);
258
258
  });
259
259
 
260
- it(`answers only for the locked set an ordinary path was never refused to begin with`, () => {
260
+ it(`answers only for the locked set: an ordinary path was never refused to begin with`, () => {
261
261
  // Tracked, but not locked: the guards never ask this of them, and a `true` here would read as "this
262
262
  // path needed a carve-out", which is a different and wrong statement.
263
263
  expect(isReviewableLockedPath(`.intentic/config/settings.json`)).toBe(false);
@@ -285,22 +285,22 @@ describe(`VERSIONED_STATE_PATHS`, () => {
285
285
  *
286
286
  * `versioned` carves an entry out of the root repo's wholesale `.intentic` exclusion, so marking one is the
287
287
  * difference between a file the owner reviews and a file the next baseline commit publishes into `git log`
288
- * forever. A credential marked by a hurried hand is not recoverable by unmarking it later the commit is
289
- * already written which is why the refusal is mechanical here rather than a rule in a comment. */
288
+ * forever. A credential marked by a hurried hand is not recoverable by unmarking it later: the commit is
289
+ * already written, which is why the refusal is mechanical here rather than a rule in a comment. */
290
290
  it(`never tracks a credential or an identity binding`, () => {
291
291
  const leaked = WORKSPACE_STATE_FILES.filter((file) => file.versioned && (file.portability === `secret` || file.portability === `identity`));
292
292
  expect(leaked.map((file) => file.path)).toEqual([]);
293
293
  });
294
294
 
295
- /* Narrower than `carry` ON PURPOSE, and this is where that stays true. The two answer different questions
296
- * carry is "does it move to a new sandbox", versioned is "should a human review it changing" so the
295
+ /* Narrower than `carry` ON PURPOSE, and this is where that stays true. The two answer different questions:
296
+ * carry is "does it move to a new sandbox", versioned is "should a human review it changing", so the
297
297
  * ledgers and the bulk are `carry` and deliberately absent: the run ledger is rewritten several times per
298
298
  * workflow step, the usage batch every few seconds a browser is open, and the transcripts run to hundreds of
299
299
  * megabytes. Tracking any of them buries the owner's code review under machine noise. */
300
300
  it(`leaves the ledgers and the bulk out even though they travel`, () => {
301
301
  for (const path of [
302
302
  `.intentic/records/workflow-runs.json`,
303
- // Split out of the tracked automations manifest precisely so a fire stops dirtying it the one
303
+ // Split out of the tracked automations manifest precisely so a fire stops dirtying it: the one
304
304
  // entry here that would be a REGRESSION rather than an oversight if it ever went tracked.
305
305
  `.intentic/records/automation-runs.json`,
306
306
  `.intentic/records/loops.json`,
@@ -315,11 +315,11 @@ describe(`VERSIONED_STATE_PATHS`, () => {
315
315
  });
316
316
 
317
317
  /* Spelled out rather than derived, so ADDING a tracked entry is a visible edit to this list and not a silent
318
- * consequence of editing the table above the review the flag itself exists to force. */
318
+ * consequence of editing the table above: the review the flag itself exists to force. */
319
319
  it(`tracks exactly the configuration slice plus the agent's own authored output`, () => {
320
320
  expect(VERSIONED_STATE_PATHS.toSorted()).toEqual([
321
321
  `.intentic/config/automations.json`,
322
- /* The connections themselves, and the entry that took the longest to earn its place it was classed
322
+ /* The connections themselves, and the entry that took the longest to earn its place: it was classed
323
323
  * `secret` on the strength of holding each capability's credential, which stopped being true when the
324
324
  * vault took the values out and left the shape behind. Connecting a deployment orchestrator, or
325
325
  * granting a connected computer shell and screen control, is the largest change made to what this
@@ -327,9 +327,9 @@ describe(`VERSIONED_STATE_PATHS`, () => {
327
327
  `.intentic/config/capabilities.json`,
328
328
  `.intentic/config/capability-dismissals.json`,
329
329
  /* The two entries the AGENT authors on its own initiative, and the reason `versioned` is not read as
330
- * config-only. Both are the sandbox acting outward a draft publishes words under the owner's name,
330
+ * config-only. Both are the sandbox acting outward: a draft publishes words under the owner's name,
331
331
  * a workspace extension is code that runs in the app and can serve HTTP with the workspace under
332
- * node:fs and both used to reach that far with no diff anywhere. Kept rather than consumed, one
332
+ * node:fs, and both used to reach that far with no diff anywhere. Kept rather than consumed, one
333
333
  * small file at a time, so tracking them yields a record instead of churn. */
334
334
  `.intentic/config/drafts/`,
335
335
  `.intentic/config/environment.Dockerfile`,
@@ -340,12 +340,12 @@ describe(`VERSIONED_STATE_PATHS`, () => {
340
340
  * behind it was not, so a commit could record turning an extension on and say nothing about what it
341
341
  * was told to do. Tracked once its declared-secret values moved to the vault. */
342
342
  `.intentic/config/extension-settings.json`,
343
- // The owner's per-extension update posture (notify / agent / auto) a standing decision about
343
+ // The owner's per-extension update posture (notify / agent / auto): a standing decision about
344
344
  // what may run unattended, which is exactly the kind of edit worth a line in `git log`.
345
345
  `.intentic/config/extension-update-policy.json`,
346
346
  `.intentic/config/loop-designs.json`,
347
347
  `.intentic/config/personas.json`,
348
- // A persona's own kit the prompt it runs on and the skills only its turns reach. Tracked for the
348
+ // A persona's own kit: the prompt it runs on and the skills only its turns reach. Tracked for the
349
349
  // reason its card is, one step further: this is the text that decides how that persona behaves.
350
350
  `.intentic/config/personas/`,
351
351
  `.intentic/config/settings.json`,
@@ -360,7 +360,7 @@ describe(`VERSIONED_STATE_PATHS`, () => {
360
360
 
361
361
  /* THE ASYMMETRY THIS CLOSED, kept as its own assertion because it is the failure rather than a detail of it:
362
362
  * the switch was tracked and the thing it switched was not, so a commit could record turning on an extension
363
- * whose code nobody else could read and a workspace extension has no install moment to review at instead. */
363
+ * whose code nobody else could read, and a workspace extension has no install moment to review at instead. */
364
364
  it(`tracks a workspace extension's code, not just the switch that enables it`, () => {
365
365
  expect(VERSIONED_STATE_PATHS).toContain(`.intentic/config/extension-enablement.json`);
366
366
  expect(VERSIONED_STATE_PATHS).toContain(`.intentic/config/workspace-extensions/`);
@@ -375,14 +375,14 @@ describe(`VERSIONED_STATE_PATHS`, () => {
375
375
  for (const path of [`.intentic/records/approvals/`, `.intentic/config/docs/`]) {
376
376
  expect([path, VERSIONED_STATE_PATHS.includes(path)]).toEqual([path, false]);
377
377
  }
378
- // Both still reach workspace search searchability is a property of the content, not of tracking.
378
+ // Both still reach workspace search: searchability is a property of the content, not of tracking.
379
379
  for (const path of [`.intentic/records/approvals/`, `.intentic/config/docs/`]) {
380
380
  const entry = WORKSPACE_STATE_FILES.find((file) => file.path === path);
381
381
  expect([path, entry?.versioned === true || entry?.authored === true]).toEqual([path, path === `.intentic/config/docs/`]);
382
382
  }
383
383
  });
384
384
 
385
- /* The composed overlay is `derived` recomposed on every boot from the custom file that IS tracked, against
385
+ /* The composed overlay is `derived`: recomposed on every boot from the custom file that IS tracked, against
386
386
  * whatever base image this container happens to be on. Tracking it would put a rewritten-at-startup file in
387
387
  * front of the owner as a change they made. */
388
388
  it(`tracks the environment overlay's source but not its composed output`, () => {
@@ -395,7 +395,7 @@ describe(`VERSIONED_STATE_PATHS`, () => {
395
395
  *
396
396
  * `stateGroupOf` reads three existing answers instead of adding a fourth, which is only sound while those answers
397
397
  * nest: reviewed and authored entries must all be `carry`, so "did a person write this" never has to be asked of
398
- * a credential. Nothing in the type system says so `versioned: true` on a `secret` entry compiles and the
398
+ * a credential. Nothing in the type system says so, `versioned: true` on a `secret` entry compiles, and the
399
399
  * failure would be quiet in the worst way: the entry would group as `secrets` (correctly kept out of the backup)
400
400
  * while the git exclude and the search floor, which read `versioned` directly, went on tracking and indexing it.
401
401
  * A credential in the owner's diff and in the search index, from one plausible-looking flag. */
@@ -439,8 +439,8 @@ describe(`state groups`, () => {
439
439
 
440
440
  /* THE LAYOUT GUARD, and the reason the folders can carry the rules at all.
441
441
  *
442
- * Five prefixes replaced five hand-kept path lists the git exclude, the search allow-list, the sync backup,
443
- * the watcher skip, the export bundle and every one of them is now only as true as the claim that an entry
442
+ * Five prefixes replaced five hand-kept path lists: the git exclude, the search allow-list, the sync backup,
443
+ * the watcher skip, the export bundle, and every one of them is now only as true as the claim that an entry
444
444
  * physically SITS in the folder its class puts it in. Nothing else checks that: the paths are literals, and a
445
445
  * credential typed into `config/` by mistake would compile, track in git, index in search and copy to the
446
446
  * owner's laptop, with each of those rules behaving exactly as designed. This is the one assertion standing
@@ -453,7 +453,7 @@ describe(`state groups`, () => {
453
453
  });
454
454
 
455
455
  /* The browser profiles' path is copied into @intentic/workspace-ignore (isBrowserProfilePath), which cannot
456
- * import this package it is the browser-safe half, deliberately free of zod and the contract surface, so
456
+ * import this package: it is the browser-safe half, deliberately free of zod and the contract surface, so
457
457
  * the platform's web bundle can take it. This is the guard that keeps the copy honest. If it fails, the
458
458
  * profiles moved and `BROWSER_PROFILE_GROUP` in _sandbox/workspace-ignore/src/constants.ts must move too, or
459
459
  * the tree will start eagerly walking a Chromium user-data dir and the watcher will report its churn. */
@@ -481,7 +481,7 @@ describe(`state groups`, () => {
481
481
  });
482
482
 
483
483
  /* WHAT THE OWNER'S MACHINE KEEPS. The sync ignored the whole state dir, so losing a sandbox lost every persona,
484
- * skill, automation, draft and transcript in it a backup that held only the source tree. These pin the two
484
+ * skill, automation, draft and transcript in it: a backup that held only the source tree. These pin the two
485
485
  * halves of the fix: the slice that now comes down, and the credentials that still must not. */
486
486
  describe(`BACKED_UP_STATE_PATHS`, () => {
487
487
  it(`splits the table in two with nothing falling between`, () => {
@@ -503,14 +503,14 @@ describe(`BACKED_UP_STATE_PATHS`, () => {
503
503
  }
504
504
  });
505
505
 
506
- it(`leaves the rebuildable bulk behind it is size, not secrecy`, () => {
506
+ it(`leaves the rebuildable bulk behind: it is size, not secrecy`, () => {
507
507
  for (const path of stateGroupPaths(`local`)) {
508
508
  expect([path, BACKED_UP_STATE_PATHS.includes(path)]).toEqual([path, false]);
509
509
  }
510
510
  });
511
511
 
512
512
  /* The distinction `portability` alone cannot draw, and the reason the `backup` flag exists. Ownership records
513
- * may never RESTORE into another sandbox that is what `identity` means, and members.json's own entry argues
513
+ * may never RESTORE into another sandbox: that is what `identity` means, and members.json's own entry argues
514
514
  * why. None of that stops the owner keeping a copy of who could drive their own machine. */
515
515
  it(`copies the ownership records that may never travel`, () => {
516
516
  for (const path of [`.intentic/identity/owner.json`, `.intentic/identity/members.json`, `.intentic/identity/workspace.json`]) {
@@ -520,7 +520,7 @@ describe(`BACKED_UP_STATE_PATHS`, () => {
520
520
 
521
521
  it(`still withholds the tokens that authenticate against this sandbox`, () => {
522
522
  expect(BACKED_UP_STATE_PATHS).not.toContain(`.intentic/identity/control-tokens.json`);
523
- // And it is the ONLY entry that opts out by hand everything else follows from its class.
523
+ // And it is the ONLY entry that opts out by hand: everything else follows from its class.
524
524
  expect(WORKSPACE_STATE_FILES.filter((file) => file.backup === false).map((file) => file.path)).toEqual([
525
525
  `.intentic/identity/control-tokens.json`,
526
526
  ]);
@@ -253,7 +253,7 @@ const STATE_FILES = [
253
253
  {
254
254
  path: ".intentic/config/automations.json",
255
255
  invalidates: [],
256
- why: "Declared by the intentic.automations extension's contributes.files `automations` is its query key, not core's.",
256
+ why: "Declared by the intentic.automations extension's contributes.files, `automations` is its query key, not core's.",
257
257
  portability: "carry",
258
258
  versioned: true,
259
259
  },
@@ -273,13 +273,13 @@ const STATE_FILES = [
273
273
  {
274
274
  path: ".intentic/records/automation-runs.json",
275
275
  invalidates: [],
276
- why: "Declared by the intentic.automations extension's contributes.files `automations` is its query key, not core's.",
276
+ why: "Declared by the intentic.automations extension's contributes.files, `automations` is its query key, not core's.",
277
277
  portability: "carry",
278
278
  },
279
279
  {
280
280
  path: ".intentic/records/approvals/",
281
281
  invalidates: [],
282
- why: "Declared by the intentic.automations extension's contributes.files `automation-approvals` is its query key, not core's.",
282
+ why: "Declared by the intentic.automations extension's contributes.files, `automation-approvals` is its query key, not core's.",
283
283
  portability: "carry",
284
284
  },
285
285
  /* The maintenance ledger and probe evidence, written by the daemon's chores-store and rendered by the
@@ -290,7 +290,7 @@ const STATE_FILES = [
290
290
  {
291
291
  path: ".intentic/records/chores/",
292
292
  invalidates: [],
293
- why: "Declared by the intentic.maintenance extension's contributes.files `maintenance-report`/`maintenance-runs` are its query keys, not core's.",
293
+ why: "Declared by the intentic.maintenance extension's contributes.files, `maintenance-report`/`maintenance-runs` are its query keys, not core's.",
294
294
  portability: "carry",
295
295
  },
296
296
  /* The documentation STAGING tree (documentation extension's paths.ts): generation writes here, the owner
@@ -300,7 +300,7 @@ const STATE_FILES = [
300
300
  {
301
301
  path: ".intentic/config/docs/",
302
302
  invalidates: [],
303
- why: "Declared by the intentic.documentation extension's contributes.files `documentation`/`documentation-runs` are its query keys, not core's.",
303
+ why: "Declared by the intentic.documentation extension's contributes.files, `documentation`/`documentation-runs` are its query keys, not core's.",
304
304
  portability: "carry",
305
305
  authored: true,
306
306
  outsideWriter: "the intentic.documentation extension's staging writes (its paths.ts)",
@@ -324,7 +324,7 @@ const STATE_FILES = [
324
324
  {
325
325
  path: ".intentic/records/loops.json",
326
326
  invalidates: [],
327
- why: "Ralph loops and their iteration history. Nothing observes it: where a RUNNING loop stands rides on the fleet roster (AgentSummary.loop), which the /events stream already pushes about once a second, and a second source invalidating on this file could only ever disagree with the card beside it. The iteration list of an ENDED loop is an on-demand read nothing renders it until someone opens it (web's useLoops, which holds no query for exactly this reason).",
327
+ why: "Ralph loops and their iteration history. Nothing observes it: where a RUNNING loop stands rides on the fleet roster (AgentSummary.loop), which the /events stream already pushes about once a second, and a second source invalidating on this file could only ever disagree with the card beside it. The iteration list of an ENDED loop is an on-demand read, nothing renders it until someone opens it (web's useLoops, which holds no query for exactly this reason).",
328
328
  portability: "carry",
329
329
  },
330
330
 
@@ -343,7 +343,7 @@ const STATE_FILES = [
343
343
  {
344
344
  path: ".intentic/records/thread-sessions.json",
345
345
  invalidates: [],
346
- why: "Thread bookkeeping (an inbound thread a Front Desk visitor, a Discord or Slack channel → sandbox conversation + provider session), written on EVERY inbound message. Nothing in the browser reads it: what a thread produces is a conversation, and the fleet board already learns about that from the agent registry's own push. Naming a key here would bill every connected browser a refetch per inbound message the request storm this table's own note warns about to refresh nothing it can see.",
346
+ why: "Thread bookkeeping (an inbound thread, a Front Desk visitor, a Discord or Slack channel, → sandbox conversation + provider session), written on EVERY inbound message. Nothing in the browser reads it: what a thread produces is a conversation, and the fleet board already learns about that from the agent registry's own push. Naming a key here would bill every connected browser a refetch per inbound message, the request storm this table's own note warns about, to refresh nothing it can see.",
347
347
  portability: "carry",
348
348
  },
349
349
  /* SPLIT, so that "what an extension is configured to do" and "the token it does it with" stop being one file.
@@ -370,7 +370,7 @@ const STATE_FILES = [
370
370
  {
371
371
  path: ".intentic/config/extension-settings.json",
372
372
  invalidates: [],
373
- why: "Held in a module-level shallowRef store per extension (web's extensionSettingsStore) with no query observer, and deliberately so: api.settings.get must answer SYNCHRONOUSLY from an extension's first activate() line, and the store outlives every component scope. A module-level QueryObserver is the one shape that would make invalidation refetch, and this app already ruled it out it detaches on the queryClient.clear() at logout (see useSandbox's sandbox-list mirror). So a remote member's setting edit reaches this browser on its next load, not live.",
373
+ why: "Held in a module-level shallowRef store per extension (web's extensionSettingsStore) with no query observer, and deliberately so: api.settings.get must answer SYNCHRONOUSLY from an extension's first activate() line, and the store outlives every component scope. A module-level QueryObserver is the one shape that would make invalidation refetch, and this app already ruled it out, it detaches on the queryClient.clear() at logout (see useSandbox's sandbox-list mirror). So a remote member's setting edit reaches this browser on its next load, not live.",
374
374
  portability: "carry",
375
375
  versioned: true,
376
376
  },
@@ -416,7 +416,7 @@ const STATE_FILES = [
416
416
  {
417
417
  path: ".intentic/records/extension-usage.json",
418
418
  invalidates: [],
419
- why: "Which of the routes each extension DECLARED it has actually called the evidence behind the permissions list on its row. The one entry here whose empty set is a RATE decision rather than an architectural one: every browser with the app open reports its batch on a timer, so wiring this to the `extensions` query would refetch the whole list every few seconds for a figure nobody is watching change. The tab reads it when it loads, which is when anyone is reading it.",
419
+ why: "Which of the routes each extension DECLARED it has actually called, the evidence behind the permissions list on its row. The one entry here whose empty set is a RATE decision rather than an architectural one: every browser with the app open reports its batch on a timer, so wiring this to the `extensions` query would refetch the whole list every few seconds for a figure nobody is watching change. The tab reads it when it loads, which is when anyone is reading it.",
420
420
  portability: "carry",
421
421
  },
422
422
  /* THE ONE ENTRY WHERE "HOLDS NO CREDENTIAL" IS TRUE AND `versioned` IS STILL WRONG, which is worth stating
@@ -433,9 +433,9 @@ const STATE_FILES = [
433
433
  {
434
434
  path: ".intentic/identity/members.json",
435
435
  invalidates: [],
436
- why: "Not this view's source at all: SandboxAccess renders the PLATFORM's invite records (apiClient.invite.list), and this file is the daemon's ENFORCED copy written first so a grant the enforcer never got is never recorded, then never read back. A change here means the two disagreed, which the write order makes fail-closed rather than stale.",
436
+ why: "Not this view's source at all: SandboxAccess renders the PLATFORM's invite records (apiClient.invite.list), and this file is the daemon's ENFORCED copy, written first so a grant the enforcer never got is never recorded, then never read back. A change here means the two disagreed, which the write order makes fail-closed rather than stale.",
437
437
  portability: "identity",
438
- note: "Re-invite collaborators from the Access tab a grant is the platform's record, and the target enforces its own copy.",
438
+ note: "Re-invite collaborators from the Access tab, a grant is the platform's record, and the target enforces its own copy.",
439
439
  },
440
440
 
441
441
  // ---- daemon-owned, nothing derives from watching them ----
@@ -455,7 +455,7 @@ const STATE_FILES = [
455
455
  invalidates: [],
456
456
  why: "AI-provider credentials and runtime homes, plus the capability and extension-settings secret vaults; each account is rendered through owner-gated provider routes.",
457
457
  portability: "secret",
458
- note: "Sign the agent's AI accounts in again on the Agent tab, then re-enter each connection's credential on Capabilities and each extension's secret settings on Extensions both arrived listed but unauthenticated.",
458
+ note: "Sign the agent's AI accounts in again on the Agent tab, then re-enter each connection's credential on Capabilities and each extension's secret settings on Extensions, both arrived listed but unauthenticated.",
459
459
  },
460
460
  /* Agent session transcripts, rewritten on every streamed token.
461
461
  *
@@ -470,7 +470,7 @@ const STATE_FILES = [
470
470
  {
471
471
  path: ".intentic/records/sessions/claude/",
472
472
  invalidates: [],
473
- why: "Agent session transcripts see the note above on why the memory notes under it stay polled.",
473
+ why: "Agent session transcripts, see the note above on why the memory notes under it stay polled.",
474
474
  portability: "carry",
475
475
  },
476
476
  {
@@ -482,7 +482,7 @@ const STATE_FILES = [
482
482
  {
483
483
  path: ".intentic/local/cache/",
484
484
  invalidates: [],
485
- why: "Rebuildable indexes and caches the iq index and its vector sidecar, the whisper model; ignored by the watcher and recreated from carried workspace content.",
485
+ why: "Rebuildable indexes and caches, the iq index and its vector sidecar, the whisper model; ignored by the watcher and recreated from carried workspace content.",
486
486
  portability: "derived",
487
487
  },
488
488
  /* Connector and extension scratch, one directory per extension under runtime/extensions/<id>
@@ -518,14 +518,14 @@ const STATE_FILES = [
518
518
  {
519
519
  path: ".intentic/local/newest-run.json",
520
520
  invalidates: [],
521
- why: "The newest daemon version that ever ran this workspace (store/newest-run.ts) a downgrade tripwire, about THIS sandbox the way rule-firings is.",
521
+ why: "The newest daemon version that ever ran this workspace (store/newest-run.ts), a downgrade tripwire, about THIS sandbox the way rule-firings is.",
522
522
  portability: "derived",
523
523
  note: "The target stamps its own daemon version on first boot.",
524
524
  },
525
525
  {
526
526
  path: ".intentic/records/verify.json",
527
527
  invalidates: [],
528
- why: "The dependency verifier's verdict memory; nothing renders it directly outcomes reach the owner as activity entries and workspace events.",
528
+ why: "The dependency verifier's verdict memory; nothing renders it directly, outcomes reach the owner as activity entries and workspace events.",
529
529
  portability: "carry",
530
530
  },
531
531
  {
@@ -539,7 +539,7 @@ const STATE_FILES = [
539
539
  invalidates: [],
540
540
  why: "Webhook secret + conclusion memory; the Pipelines view reads it through /ci/runs, not off disk.",
541
541
  portability: "secret",
542
- note: "Re-add the CI webhook on the Pipelines view its secret is per-sandbox.",
542
+ note: "Re-add the CI webhook on the Pipelines view, its secret is per-sandbox.",
543
543
  },
544
544
  /* THE ONE ENTRY THAT OPTS OUT OF THE BACKUP, and the reason the flag exists rather than the rule simply
545
545
  * reading `portability !== "derived"`. It is `identity` like the three below it, so the derived answer would
@@ -555,7 +555,7 @@ const STATE_FILES = [
555
555
  why: "Hashed control tokens (the ACP editor bridge, and anything else driving this sandbox from outside), listed on demand by the owner.",
556
556
  portability: "identity",
557
557
  backup: false,
558
- note: "Mint fresh control tokens the old ones authenticate against the source sandbox.",
558
+ note: "Mint fresh control tokens, the old ones authenticate against the source sandbox.",
559
559
  },
560
560
  {
561
561
  path: ".intentic/identity/owner.json",
@@ -585,12 +585,12 @@ const STATE_FILES = [
585
585
  invalidates: [],
586
586
  why: "Browser-login profiles: Chromium rewrites these constantly. Descent-ignored by the watcher outright.",
587
587
  portability: "derived",
588
- note: "Log the agent's browser back into any site it needs profiles do not travel.",
588
+ note: "Log the agent's browser back into any site it needs, profiles do not travel.",
589
589
  },
590
590
  {
591
591
  path: ".intentic/local/extensions/",
592
592
  invalidates: [],
593
- why: "Extension checkouts whole git clones. The `extensions` query is driven by the capability manifest above, not by their contents.",
593
+ why: "Extension checkouts, whole git clones. The `extensions` query is driven by the capability manifest above, not by their contents.",
594
594
  portability: "derived",
595
595
  note: "Extensions re-clone from the capability manifest on the target's next reconcile.",
596
596
  },