@intentic/sandbox-contract 1.223.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.
- package/README.md +14 -14
- package/dist/agent-catalog.js +3 -3
- package/dist/agent-catalog.js.map +1 -1
- package/dist/chores/chores.js +43 -43
- package/dist/chores/chores.js.map +1 -1
- package/dist/chores/extension-update.js +2 -2
- package/dist/chores/extension-update.js.map +1 -1
- package/dist/chores/fix-deps.js +1 -1
- package/dist/chores/fix-deps.js.map +1 -1
- package/dist/chores/probes.js +1 -1
- package/dist/chores/probes.js.map +1 -1
- package/dist/chores/prompt.d.ts.map +1 -1
- package/dist/chores/prompt.js +3 -3
- package/dist/chores/prompt.js.map +1 -1
- package/dist/chores/verdict.js +2 -2
- package/dist/chores/verdict.js.map +1 -1
- package/dist/contracts/capabilities.contract.d.ts +41 -0
- package/dist/contracts/capabilities.contract.d.ts.map +1 -1
- package/dist/contracts/exit.contract.d.ts +80 -0
- package/dist/contracts/exit.contract.d.ts.map +1 -0
- package/dist/contracts/exit.contract.js +13 -0
- package/dist/contracts/exit.contract.js.map +1 -0
- package/dist/contracts/ports.contract.d.ts +11 -0
- package/dist/contracts/ports.contract.d.ts.map +1 -1
- package/dist/contracts/settings.contract.d.ts +12 -0
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/events.d.ts +4 -4
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +5 -5
- package/dist/events.js.map +1 -1
- package/dist/fast-tier.d.ts +9 -0
- package/dist/fast-tier.d.ts.map +1 -0
- package/dist/fast-tier.js +19 -0
- package/dist/fast-tier.js.map +1 -0
- package/dist/history-state.js +3 -3
- package/dist/history-state.js.map +1 -1
- package/dist/index.d.ts +214 -68
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/model-order.d.ts +1 -0
- package/dist/model-order.d.ts.map +1 -1
- package/dist/model-order.js +5 -0
- package/dist/model-order.js.map +1 -1
- package/dist/output-fields.d.ts.map +1 -1
- package/dist/output-fields.js +2 -2
- package/dist/output-fields.js.map +1 -1
- package/dist/prompt-complexity.d.ts +19 -0
- package/dist/prompt-complexity.d.ts.map +1 -0
- package/dist/prompt-complexity.js +83 -0
- package/dist/prompt-complexity.js.map +1 -0
- package/dist/publish-drafts.js +2 -2
- package/dist/publish-drafts.js.map +1 -1
- package/dist/schemas.d.ts +236 -0
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +116 -2
- package/dist/schemas.js.map +1 -1
- package/dist/workflow-faults.js +3 -3
- package/dist/workflow-faults.js.map +1 -1
- package/dist/workspace-state.d.ts +20 -20
- package/dist/workspace-state.d.ts.map +1 -1
- package/dist/workspace-state.js +20 -20
- package/dist/workspace-state.js.map +1 -1
- package/package.json +5 -5
- package/src/agent-catalog.test.ts +25 -25
- package/src/agent-catalog.ts +81 -81
- package/src/agent-run-model.test.ts +3 -3
- package/src/agent-run-model.ts +8 -8
- package/src/capability-env.ts +3 -3
- package/src/capability-ledger.test.ts +13 -13
- package/src/capability-secrets.ts +5 -5
- package/src/chores/chores.test.ts +1 -1
- package/src/chores/chores.ts +109 -109
- package/src/chores/digest.test.ts +1 -1
- package/src/chores/digest.ts +3 -3
- package/src/chores/extension-update.ts +5 -5
- package/src/chores/fix-deps.ts +4 -4
- package/src/chores/probes.test.ts +6 -6
- package/src/chores/probes.ts +17 -17
- package/src/chores/prompt.ts +9 -9
- package/src/chores/stack.test.ts +3 -3
- package/src/chores/stack.ts +17 -17
- package/src/chores/verdict.test.ts +20 -20
- package/src/chores/verdict.ts +25 -25
- package/src/contract-lock.test.ts +1 -1
- package/src/contract-lock.ts +7 -7
- package/src/contracts/activity.contract.ts +1 -1
- package/src/contracts/agent.contract.ts +2 -2
- package/src/contracts/agents.contract.ts +11 -11
- package/src/contracts/automations.contract.ts +5 -5
- package/src/contracts/capabilities.contract.ts +7 -7
- package/src/contracts/chores.contract.ts +3 -3
- package/src/contracts/ci.contract.ts +2 -2
- package/src/contracts/claude.contract.ts +3 -3
- package/src/contracts/drafts.contract.ts +1 -1
- package/src/contracts/endpoints.contract.ts +2 -2
- package/src/contracts/exit.contract.ts +42 -0
- package/src/contracts/extensions.contract.ts +8 -8
- package/src/contracts/git.contract.ts +6 -6
- package/src/contracts/grok.contract.ts +4 -4
- package/src/contracts/host.contract.ts +5 -5
- package/src/contracts/intentic.contract.ts +2 -2
- package/src/contracts/logs.contract.ts +1 -1
- package/src/contracts/loops.contract.ts +8 -8
- package/src/contracts/personas.contract.ts +8 -8
- package/src/contracts/ports.contract.ts +1 -1
- package/src/contracts/prepush.contract.ts +2 -2
- package/src/contracts/providers.contract.ts +4 -4
- package/src/contracts/public.contract.ts +1 -1
- package/src/contracts/push.contract.ts +2 -2
- package/src/contracts/secrets.contract.ts +2 -2
- package/src/contracts/sessions.contract.ts +2 -2
- package/src/contracts/settings.contract.ts +3 -3
- package/src/contracts/share.contract.ts +1 -1
- package/src/contracts/skills.contract.ts +2 -2
- package/src/contracts/system.contract.ts +9 -9
- package/src/contracts/usage.contract.ts +1 -1
- package/src/contracts/vpn.contract.ts +5 -5
- package/src/contracts/workflows.contract.ts +12 -12
- package/src/contracts/workspace.contract.ts +13 -13
- package/src/conversation-ids.ts +8 -8
- package/src/events.test.ts +3 -3
- package/src/events.ts +142 -142
- package/src/fast-tier.test.ts +88 -0
- package/src/fast-tier.ts +72 -0
- package/src/history-state.ts +15 -15
- package/src/host-protocol.ts +7 -7
- package/src/hostnames.test.ts +1 -1
- package/src/hostnames.ts +15 -15
- package/src/index.ts +13 -8
- package/src/listener-protocol.ts +12 -12
- package/src/model-order.test.ts +11 -11
- package/src/model-order.ts +52 -30
- package/src/output-fields.ts +11 -11
- package/src/path-refs.test.ts +4 -4
- package/src/path-refs.ts +6 -6
- package/src/prompt-complexity.test.ts +160 -0
- package/src/prompt-complexity.ts +271 -0
- package/src/publish-drafts.ts +8 -8
- package/src/quick-model.test.ts +11 -11
- package/src/quick-model.ts +15 -15
- package/src/routes.test.ts +11 -5
- package/src/routes.ts +19 -19
- package/src/runtime-state.test.ts +1 -1
- package/src/runtime-state.ts +22 -22
- package/src/schemas.test.ts +8 -8
- package/src/schemas.ts +1238 -921
- package/src/search-globs.test.ts +2 -2
- package/src/search-globs.ts +6 -6
- package/src/session-names.ts +5 -5
- package/src/share-paths.test.ts +1 -1
- package/src/share-paths.ts +7 -7
- package/src/sse.ts +1 -1
- package/src/state-portability.ts +8 -8
- package/src/terminal-protocol.ts +3 -3
- package/src/title.test.ts +9 -9
- package/src/title.ts +21 -21
- package/src/tunnel-ids.test.ts +3 -3
- package/src/tunnel-ids.ts +7 -7
- package/src/versions.test.ts +3 -3
- package/src/versions.ts +6 -6
- package/src/workflow-faults.test.ts +6 -6
- package/src/workflow-faults.ts +14 -14
- package/src/workspace-state.test.ts +32 -32
- package/src/workspace-state.ts +139 -139
package/src/workflow-faults.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* WHY A WORKFLOW GRAPH IS NOT RUNNABLE
|
|
1
|
+
/* WHY A WORKFLOW GRAPH IS NOT RUNNABLE, the rules that `WorkflowSchema` cannot state, because each of them is
|
|
2
2
|
* about the graph rather than about one field.
|
|
3
3
|
*
|
|
4
4
|
* A rule per function, so the reason a graph is refused reads as one sentence in one place. They are
|
|
@@ -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
|
|
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
|
}
|
|
@@ -49,24 +49,24 @@ const stepFaults = (step: WorkflowStep, ids: ReadonlySet<string>): string[] => {
|
|
|
49
49
|
),
|
|
50
50
|
);
|
|
51
51
|
}
|
|
52
|
-
/* A STEP THAT DECLARES NOTHING IS NOT A FAULT
|
|
52
|
+
/* A STEP THAT DECLARES NOTHING IS NOT A FAULT, it is the ordinary step, and this used to refuse it.
|
|
53
53
|
*
|
|
54
54
|
* The rule was borrowed from loops (`loopCanConverge`), where it is right: a LOOP is started to repeat
|
|
55
55
|
* until something is true, so one with nothing to produce and nothing to check has no reason to run twice
|
|
56
56
|
* and the dialog is correct to grey out its button. A STEP is not started to repeat. It is one agent
|
|
57
|
-
* session with a job, and the job being done is the turn ending
|
|
57
|
+
* session with a job, and the job being done is the turn ending, which is exactly what the loop machinery
|
|
58
58
|
* already does with it (loop-stop's `readDocument` answers `done` for a `none` output, so iteration 1 is
|
|
59
59
|
* the only iteration).
|
|
60
60
|
*
|
|
61
61
|
* Keeping the rule here forced every step to declare an output or a check before the graph would save, and
|
|
62
|
-
* the cheapest way to satisfy it was a `claim
|
|
62
|
+
* the cheapest way to satisfy it was a `claim`, which buys a verdict file nobody reads, a page of contract
|
|
63
63
|
* in the prompt, and a way for a step that did the work to fail for not having described it.
|
|
64
64
|
*/
|
|
65
65
|
return faults;
|
|
66
66
|
};
|
|
67
67
|
|
|
68
68
|
/* Two steps continuing the SAME session is the one graph that is legal on paper and broken in practice: both
|
|
69
|
-
* would run on one conversation, in parallel, against one worktree and one turn mutex
|
|
69
|
+
* would run on one conversation, in parallel, against one worktree and one turn mutex, so they would serialize
|
|
70
70
|
* on a lock neither knows about and the second would inherit a session the first had moved on. A predecessor
|
|
71
71
|
* can be continued once; anything else that needs its result takes it as a handover. */
|
|
72
72
|
const sharedContinuationFaults = (steps: readonly WorkflowStep[]): string[] => {
|
|
@@ -110,8 +110,8 @@ const cycleFaults = (steps: readonly WorkflowStep[]): string[] => {
|
|
|
110
110
|
return faults;
|
|
111
111
|
};
|
|
112
112
|
|
|
113
|
-
/* A GATE THAT CANNOT BE ANSWERED. Every rule here is about the gate against the GRAPH
|
|
114
|
-
* them can live in the schema
|
|
113
|
+
/* A GATE THAT CANNOT BE ANSWERED. Every rule here is about the gate against the GRAPH, which is why none of
|
|
114
|
+
* them can live in the schema, and all of them fail the same expensive way if unchecked: the run spends its
|
|
115
115
|
* whole fan-out of sessions and then answers `blocked`, on every commit, for a reason nobody sees until they
|
|
116
116
|
* go reading the daemon's log.
|
|
117
117
|
*
|
|
@@ -136,16 +136,16 @@ 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
|
|
139
|
+
return [`The gate reads "${gate.field}", which is a list: a release decision has to be one value.`];
|
|
140
140
|
}
|
|
141
|
-
// The schema refuses this on save (pass is min(1)), but the designer edits drafts the schema never sees
|
|
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.
|
|
143
143
|
if (gate.pass.length === 0) {
|
|
144
144
|
return [`The gate names no passing values, so no run could ever ship.`];
|
|
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
|
|
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
|
|
@@ -163,13 +163,13 @@ export const workflowFaults = (workflow: Pick<Workflow, "steps" | "gate">): stri
|
|
|
163
163
|
];
|
|
164
164
|
};
|
|
165
165
|
|
|
166
|
-
/* WHAT ONLY A RUN CAN BE WRONG ABOUT
|
|
166
|
+
/* WHAT ONLY A RUN CAN BE WRONG ABOUT, kept apart from the rules above because it is not about the graph, and
|
|
167
167
|
* the graph is what gets SAVED. A design whose steps take their goal and instruction from the request is a
|
|
168
168
|
* perfectly good design; it is only unrunnable on the particular run that forgot to bring one.
|
|
169
169
|
*
|
|
170
170
|
* Which is why this cannot be a save-time rule and must not become one: refusing to save such a workflow would
|
|
171
|
-
* outlaw the entire point of a workflow being a SHAPE. The check belongs at the two doors that start runs
|
|
172
|
-
* run route and the gate's webhook
|
|
171
|
+
* outlaw the entire point of a workflow being a SHAPE. The check belongs at the two doors that start runs, the
|
|
172
|
+
* run route and the gate's webhook, and it has to be there rather than left to fail later, because "later"
|
|
173
173
|
* means every session in the fan-out has already been paid for before anyone finds out the model was handed an
|
|
174
174
|
* empty instruction.
|
|
175
175
|
*/
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
289
|
-
* already written
|
|
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"
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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)
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
443
|
-
* the watcher skip, the export bundle
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
]);
|