@intentic/sandbox-contract 1.233.0 → 1.234.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 +4 -3
- package/dist/agent-catalog.d.ts +2 -1
- package/dist/agent-catalog.d.ts.map +1 -1
- package/dist/agent-catalog.js +19 -13
- package/dist/agent-catalog.js.map +1 -1
- package/dist/chores/chores.d.ts.map +1 -1
- package/dist/chores/chores.js +36 -1
- package/dist/chores/chores.js.map +1 -1
- package/dist/chores/probes.d.ts.map +1 -1
- package/dist/chores/probes.js +70 -0
- package/dist/chores/probes.js.map +1 -1
- package/dist/command-classes.d.ts +5 -2
- package/dist/command-classes.d.ts.map +1 -1
- package/dist/command-classes.js +34 -13
- package/dist/command-classes.js.map +1 -1
- package/dist/contracts/automations.contract.d.ts +30 -0
- package/dist/contracts/automations.contract.d.ts.map +1 -1
- package/dist/contracts/chores.contract.d.ts +17 -0
- package/dist/contracts/chores.contract.d.ts.map +1 -1
- package/dist/contracts/extensions.contract.d.ts +2 -0
- package/dist/contracts/extensions.contract.d.ts.map +1 -1
- package/dist/contracts/extensions.contract.js.map +1 -1
- package/dist/contracts/issues.contract.d.ts +89 -0
- package/dist/contracts/issues.contract.d.ts.map +1 -0
- package/dist/contracts/issues.contract.js +50 -0
- package/dist/contracts/issues.contract.js.map +1 -0
- package/dist/contracts/runner.contract.d.ts +102 -102
- package/dist/contracts/settings.contract.d.ts +12 -0
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/credential-material.d.ts +2 -0
- package/dist/credential-material.d.ts.map +1 -0
- package/dist/credential-material.js +36 -0
- package/dist/credential-material.js.map +1 -0
- package/dist/definition.d.ts +8 -0
- package/dist/definition.d.ts.map +1 -1
- package/dist/history-state.d.ts.map +1 -1
- package/dist/history-state.js +1 -0
- package/dist/history-state.js.map +1 -1
- package/dist/hostnames.d.ts +2 -0
- package/dist/hostnames.d.ts.map +1 -1
- package/dist/hostnames.js +3 -1
- package/dist/hostnames.js.map +1 -1
- package/dist/index.d.ts +268 -116
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/runtime-state.d.ts +9 -0
- package/dist/runtime-state.d.ts.map +1 -1
- package/dist/runtime-state.js +3 -0
- package/dist/runtime-state.js.map +1 -1
- package/dist/schemas/agent.d.ts +6 -0
- package/dist/schemas/agent.d.ts.map +1 -1
- package/dist/schemas/agent.js +2 -1
- package/dist/schemas/agent.js.map +1 -1
- package/dist/schemas/automations.d.ts +45 -0
- package/dist/schemas/automations.d.ts.map +1 -1
- package/dist/schemas/automations.js +6 -1
- package/dist/schemas/automations.js.map +1 -1
- package/dist/schemas/extension-updates.d.ts +2 -0
- package/dist/schemas/extension-updates.d.ts.map +1 -1
- package/dist/schemas/extension-updates.js +3 -1
- package/dist/schemas/extension-updates.js.map +1 -1
- package/dist/schemas/issues.d.ts +324 -0
- package/dist/schemas/issues.d.ts.map +1 -0
- package/dist/schemas/issues.js +107 -0
- package/dist/schemas/issues.js.map +1 -0
- package/dist/schemas/maintenance.d.ts +62 -1
- package/dist/schemas/maintenance.d.ts.map +1 -1
- package/dist/schemas/maintenance.js +22 -1
- package/dist/schemas/maintenance.js.map +1 -1
- package/dist/schemas/settings.d.ts +6 -0
- package/dist/schemas/settings.d.ts.map +1 -1
- package/dist/schemas/settings.js +4 -0
- package/dist/schemas/settings.js.map +1 -1
- package/dist/schemas/terminal.d.ts.map +1 -1
- package/dist/schemas/terminal.js.map +1 -1
- package/dist/schemas/webext.d.ts +24 -0
- package/dist/schemas/webext.d.ts.map +1 -1
- package/dist/schemas/webext.js +9 -0
- package/dist/schemas/webext.js.map +1 -1
- package/dist/webext-links.d.ts +1 -0
- package/dist/webext-links.d.ts.map +1 -1
- package/dist/webext-links.js +1 -0
- package/dist/webext-links.js.map +1 -1
- package/dist/workspace-state.d.ts +10 -6
- package/dist/workspace-state.d.ts.map +1 -1
- package/dist/workspace-state.js +13 -8
- package/dist/workspace-state.js.map +1 -1
- package/package.json +8 -8
- package/src/agent-catalog.test.ts +55 -54
- package/src/agent-catalog.ts +29 -22
- package/src/capability-ledger.test.ts +11 -2
- package/src/chores/chores.test.ts +1 -1
- package/src/chores/chores.ts +66 -1
- package/src/chores/probes.test.ts +65 -0
- package/src/chores/probes.ts +133 -1
- package/src/chores/verdict.test.ts +29 -19
- package/src/command-classes.test.ts +71 -4
- package/src/command-classes.ts +112 -22
- package/src/contracts/extensions.contract.ts +3 -2
- package/src/contracts/issues.contract.ts +60 -0
- package/src/credential-material.test.ts +120 -0
- package/src/credential-material.ts +100 -0
- package/src/history-state.ts +12 -0
- package/src/hostnames.ts +18 -2
- package/src/index.ts +5 -0
- package/src/routes.test.ts +4 -2
- package/src/runtime-state.ts +16 -0
- package/src/schemas/agent.ts +15 -2
- package/src/schemas/automations.ts +14 -2
- package/src/schemas/extension-updates.ts +3 -1
- package/src/schemas/issues.ts +279 -0
- package/src/schemas/maintenance.ts +51 -1
- package/src/schemas/settings.ts +32 -0
- package/src/schemas/terminal.ts +6 -4
- package/src/schemas/webext.ts +29 -0
- package/src/tunnel-ids.test.ts +3 -1
- package/src/webext-links.ts +10 -0
- package/src/workspace-state.test.ts +3 -1
- package/src/workspace-state.ts +27 -37
|
@@ -13,7 +13,7 @@ import { WorkspaceHotspotSchema, WorkspaceKeyModuleSchema } from "./codebase-hea
|
|
|
13
13
|
* signals things the daemon already knows, the resident iq index's health ranking, the package manifests it
|
|
14
14
|
* reads for the dependency graph, its own node version. Recomputed per request; all of it is cheap. */
|
|
15
15
|
|
|
16
|
-
export const PROBE_IDS = ["outdated", "audit", "knip", "jscpd", "ui", "bundle"] as const;
|
|
16
|
+
export const PROBE_IDS = ["outdated", "audit", "knip", "jscpd", "ui", "bundle", "mutation"] as const;
|
|
17
17
|
export const ProbeIdSchema = z.enum(PROBE_IDS);
|
|
18
18
|
export type ProbeId = z.infer<typeof ProbeIdSchema>;
|
|
19
19
|
// One dependency the registry has moved past. `kind` is the SEMVER distance, which is the whole reason this is
|
|
@@ -165,6 +165,55 @@ export const BundleSchema = z.object({
|
|
|
165
165
|
.describe("What is in it, piece by piece."),
|
|
166
166
|
});
|
|
167
167
|
export type Bundle = z.infer<typeof BundleSchema>;
|
|
168
|
+
/* WHAT THE SUITE WOULD NOTICE IF THE CODE BROKE. Coverage says a line ran; this says an assertion depended on it.
|
|
169
|
+
*
|
|
170
|
+
* The distinction is the whole reason this probe exists, and it is not theoretical here. Measured on
|
|
171
|
+
* sandbox-contract's chore module — 109 hand-written tests, every line covered — 16 of 58 mutants survived, and
|
|
172
|
+
* one of them flips the zero boundary in `bucketOf` that digest.ts's own comment argues is load-bearing. The test
|
|
173
|
+
* that was supposed to hold it (`expect(bucketOf(0)).not.toBe(bucketOf(1))`) is written in the careful,
|
|
174
|
+
* deliberately un-brittle style, and that is exactly why it cannot see the change: with the boundary moved the two
|
|
175
|
+
* values are still different, so the assertion still passes.
|
|
176
|
+
*
|
|
177
|
+
* That is the failure this measures and nothing else in the repository can. A linter sees the assertion's shape,
|
|
178
|
+
* not its power; a coverage report sees the line, not whether anything checked it. Only killing the code and
|
|
179
|
+
* watching what the suite says distinguishes a test from a test-shaped thing.
|
|
180
|
+
*
|
|
181
|
+
* SURVIVORS, NOT JUST A SCORE. A percentage is a mood; `bucketOf: count <= 0 → count < 0 survives` is a morning's
|
|
182
|
+
* work with the answer already in it. The score decides whether the chore speaks, the survivors are what makes it
|
|
183
|
+
* worth speaking about. */
|
|
184
|
+
export const MutationScoreSchema = z.object({
|
|
185
|
+
score: z
|
|
186
|
+
.number()
|
|
187
|
+
.describe("The share of injected faults the suite caught. Not a coverage figure: coverage says a line ran, this says an assertion depended on it."),
|
|
188
|
+
killed: z.number().int().nonnegative().describe("Faults the suite caught."),
|
|
189
|
+
survived: z.number().int().nonnegative().describe("Faults it did not: code that can be broken with every test still green."),
|
|
190
|
+
/* Mutants that never got a verdict: ones that would not compile, and ones the configuration ignored. Kept
|
|
191
|
+
* apart from both counts above, and OUT of the score, because an unmeasured mutant is not evidence either
|
|
192
|
+
* way — the same conflation `unavailable` exists to prevent one level up.
|
|
193
|
+
*
|
|
194
|
+
* A timeout is deliberately NOT here. Stryker counts it as detected, on the reasoning that a mutant which
|
|
195
|
+
* hangs the suite is one the suite noticed, and this follows Stryker's arithmetic rather than inventing a
|
|
196
|
+
* second definition of the same word: the number on the row has to mean what the tool that produced it
|
|
197
|
+
* means, or the row is quietly arguing with its own evidence. */
|
|
198
|
+
inconclusive: z
|
|
199
|
+
.number()
|
|
200
|
+
.int()
|
|
201
|
+
.nonnegative()
|
|
202
|
+
.describe("Faults it never got a verdict on, because they would not compile or were configured out. Left out of the score entirely, since neither answer is known."),
|
|
203
|
+
/* The worst offenders, named. Capped, and the cap is the point: a survivor list is only useful while it is
|
|
204
|
+
* short enough to act on, and the rest are still there on the next run. */
|
|
205
|
+
survivors: z
|
|
206
|
+
.array(
|
|
207
|
+
z.object({
|
|
208
|
+
file: z.string().describe("Where it is."),
|
|
209
|
+
line: z.number().int().nonnegative().describe("Which line."),
|
|
210
|
+
mutator: z.string().describe("What was changed, in the mutation tool's own vocabulary."),
|
|
211
|
+
replacement: z.string().describe("What it became, so a reader can judge whether it matters without opening the file."),
|
|
212
|
+
}),
|
|
213
|
+
)
|
|
214
|
+
.describe("The surviving faults themselves. A percentage is a mood; a named line with the change that went unnoticed is a morning's work."),
|
|
215
|
+
});
|
|
216
|
+
export type MutationScore = z.infer<typeof MutationScoreSchema>;
|
|
168
217
|
/* One probe's cached result. The three states are deliberately distinct, because a panel that collapses them
|
|
169
218
|
* lies about the most important case:
|
|
170
219
|
* ok the tool ran and reported. `facts` carries its findings, including "nothing found", which is
|
|
@@ -187,6 +236,7 @@ export const ProbeFactsSchema = z.discriminatedUnion("id", [
|
|
|
187
236
|
z.object({ id: z.literal("jscpd"), duplication: DuplicationSchema }),
|
|
188
237
|
z.object({ id: z.literal("ui"), scan: UiScanSchema }),
|
|
189
238
|
z.object({ id: z.literal("bundle"), bundle: BundleSchema }),
|
|
239
|
+
z.object({ id: z.literal("mutation"), mutation: MutationScoreSchema }),
|
|
190
240
|
]);
|
|
191
241
|
export type ProbeFacts = z.infer<typeof ProbeFactsSchema>;
|
|
192
242
|
export const ProbeResultSchema = z.object({
|
package/src/schemas/settings.ts
CHANGED
|
@@ -422,6 +422,38 @@ export const SandboxSettingsSchema = z.object({
|
|
|
422
422
|
.describe(
|
|
423
423
|
"Whether a version the agent is about to pin is checked against the package's own registry first. Facts only, or facts plus the name of a maintained replacement where the registry agrees the current choice has been abandoned. It tells the agent and lets it decide rather than refusing, because matching a version your project already uses is usually the right answer and a gate would fight it.",
|
|
424
424
|
),
|
|
425
|
+
/* DOES THE TEST IT JUST WROTE ACTUALLY TEST THE CHANGE?
|
|
426
|
+
*
|
|
427
|
+
* A model writes a test that passes, and a passing test is the thing it was asked for, so nothing in the loop
|
|
428
|
+
* objects. The failure this catches is the one where the test would have passed BEFORE the change too: it
|
|
429
|
+
* exercises the code without depending on the behaviour that was added, and it will keep passing when that
|
|
430
|
+
* behaviour is broken later. It type-checks, it lints, `pnpm verify` is green, and the suite has grown a test
|
|
431
|
+
* that can never fail.
|
|
432
|
+
*
|
|
433
|
+
* The check is a differential, and it is cheap for one specific reason: in an agent turn we already know
|
|
434
|
+
* exactly which source files changed. So the test is re-run with those files' HEAD contents served in place
|
|
435
|
+
* of the working copies, and the answer is one bit — did it still pass? A test that passes against the old
|
|
436
|
+
* code did not test the new code. This is one-mutant mutation testing where the mutant is "your change,
|
|
437
|
+
* reverted", which is the cheapest useful mutant there is and the only one already known for free.
|
|
438
|
+
*
|
|
439
|
+
* NOTHING IS WRITTEN TO THE TREE. The HEAD copies are served through a vite `load` hook in a generated config,
|
|
440
|
+
* never checked out over the working files — a hook that could leave a half-reverted tree behind on a crash
|
|
441
|
+
* would be trading a whole turn's work for a lint-grade signal.
|
|
442
|
+
*
|
|
443
|
+
* IT REPORTS AND GETS OUT OF THE WAY, for the same reason the freshness check above does. Two legitimate
|
|
444
|
+
* cases pass on HEAD and must not be fought: a test written BEFORE its implementation (it fails now, which
|
|
445
|
+
* the agent can see without help), and a pure refactor, where a test that keeps passing is the entire point.
|
|
446
|
+
* Distinguishing those from a weak test needs intent, so the fact rides back as context and the model decides.
|
|
447
|
+
*
|
|
448
|
+
* Scoped to source changed in the SAME package as the test, because that is what a package's own vitest run
|
|
449
|
+
* loads from source; a cross-package import resolves to the other package's built output, where there is
|
|
450
|
+
* nothing to swap. Off by default like every flag here, and off wires no hook at all. */
|
|
451
|
+
testFaultDetection: z
|
|
452
|
+
.boolean()
|
|
453
|
+
.default(false)
|
|
454
|
+
.describe(
|
|
455
|
+
"After the agent writes a test, re-run it against the code as it was before this turn's changes. A test that still passes did not test the change — it will pass just as happily when that behaviour breaks. Reports what it finds and lets the agent decide, because a test written before its implementation and a pure refactor both pass this honestly.",
|
|
456
|
+
),
|
|
425
457
|
outputCleaners: z
|
|
426
458
|
.string()
|
|
427
459
|
.default("")
|
package/src/schemas/terminal.ts
CHANGED
|
@@ -10,10 +10,12 @@ import { z } from "zod";
|
|
|
10
10
|
// Bash commands run in (live-watchable, AI-marked in the UI; running:false once every window is a finished
|
|
11
11
|
// command's dead pane, which is what lets the panel sweep it), `job` = a job-* session the daemon's terminal
|
|
12
12
|
// runner executes user-triggered flows in (capability adds, infra check), `process` = a managed background
|
|
13
|
-
// process
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
13
|
+
// process: a supervised daemon child for an extension's declared processes (name `svc-<key>`, whose
|
|
14
|
+
// "terminal" is a tail of the service's log file), or a tmux-riding one for dockerd and the local model
|
|
15
|
+
// servers, which outlive daemon restarts on purpose. Surfaced in the panel's background-processes popover
|
|
16
|
+
// with read-only log views, never as a killable tab; running is the actual process. A process row that maps
|
|
17
|
+
// to an installed extension's declared process carries extensionId+processName, the address for its
|
|
18
|
+
// /extensions start/stop routes. The
|
|
17
19
|
// `{name}` kill-route param is a bare string validated in the handler (a bad name is a BAD_REQUEST) since the
|
|
18
20
|
// same charset gates a `tmux kill-session -t` shell-out. The agent's BROWSER is deliberately NOT one of these
|
|
19
21
|
// kinds: a Chromium with its own tab strip is a surface in its own right, not a pane in the terminal panel, so
|
package/src/schemas/webext.ts
CHANGED
|
@@ -77,3 +77,32 @@ export const WebExtSessionImportSchema = z.object({
|
|
|
77
77
|
cookies: z.array(WebExtCookieSchema).min(1).max(300),
|
|
78
78
|
});
|
|
79
79
|
export type WebExtSessionImport = z.infer<typeof WebExtSessionImportSchema>;
|
|
80
|
+
|
|
81
|
+
/* ---- and the same door in the other direction: LENDING a sandbox session to the person's own browser ----
|
|
82
|
+
*
|
|
83
|
+
* The case this answers is the one no amount of streaming quality can: a passkey bound to an authenticator the
|
|
84
|
+
* person physically holds, a hardware second factor that has to be touched, an employer's SSO that checks the
|
|
85
|
+
* device. Driving the sandbox's browser remotely is not a worse experience on those sites, it is an impossible
|
|
86
|
+
* one — so the session goes to the browser the account was actually enrolled on, the person finishes the step
|
|
87
|
+
* as themselves, and `connect_site` hands it back.
|
|
88
|
+
*
|
|
89
|
+
* Same door and same rule as the import above: the cookies travel on the extension's own HTTPS request, never
|
|
90
|
+
* as a socket answer, because socket answers are MCP results and MCP results land in the model's context. */
|
|
91
|
+
export const WebExtSessionExportSchema = z.object({
|
|
92
|
+
// The `browser`-kind capability to lend FROM. Named by the agent out of the roster it can already read.
|
|
93
|
+
account: z.string().min(1),
|
|
94
|
+
// The registrable domain to lend, and the reason this cannot lend a whole profile: a profile holds every
|
|
95
|
+
// account its owner ever connected, and handing all of it over because one site got stuck is the kind of
|
|
96
|
+
// over-broad grant that is invisible until it matters.
|
|
97
|
+
domain: z.string().min(1),
|
|
98
|
+
});
|
|
99
|
+
export type WebExtSessionExport = z.infer<typeof WebExtSessionExportSchema>;
|
|
100
|
+
|
|
101
|
+
export const WebExtSessionExportResultSchema = z.object({
|
|
102
|
+
ok: z.boolean(),
|
|
103
|
+
// What the owner reads and the agent reads back. Never carries a cookie name or a value.
|
|
104
|
+
message: z.string(),
|
|
105
|
+
// Only ever read by the extension, which writes it straight into this browser's own cookie store.
|
|
106
|
+
cookies: z.array(WebExtCookieSchema).optional(),
|
|
107
|
+
});
|
|
108
|
+
export type WebExtSessionExportResult = z.infer<typeof WebExtSessionExportResultSchema>;
|
package/src/tunnel-ids.test.ts
CHANGED
|
@@ -42,7 +42,9 @@ test("slots are not derivable from the sandbox id: only from the token behind it
|
|
|
42
42
|
expect(portSlotsFromToken(OTHER)).not.toEqual(portSlotsFromToken(TOKEN));
|
|
43
43
|
// No slot leaks the id it will be paired with in `port-<slot>-<sandboxId>`.
|
|
44
44
|
const id = sandboxIdFromToken(TOKEN);
|
|
45
|
-
|
|
45
|
+
// Load-bearing, and not merely tidy: `not.toContain(undefined)` is true of every slot list ever produced,
|
|
46
|
+
// so without a real id the leak check below would pass on a function that had stopped deriving one at all.
|
|
47
|
+
expect(id).toEqual(expect.any(String));
|
|
46
48
|
expect(portSlotsFromToken(TOKEN)).not.toContain(id);
|
|
47
49
|
// And none of them is the old fixed alphabet, which is what made the hostnames guessable.
|
|
48
50
|
expect(portSlotsFromToken(TOKEN).some((slot) => slot.length === 1)).toBe(false);
|
package/src/webext-links.ts
CHANGED
|
@@ -23,6 +23,16 @@ export const webextConnectUrl = (sandboxUrl: string): string => `${sandboxUrl.re
|
|
|
23
23
|
* and the one thing that would be catastrophic to leak is the one thing the model never sees. */
|
|
24
24
|
export const webextSessionUrl = (sandboxUrl: string): string => `${sandboxUrl.replace(/\/$/, "")}/system/webext/session`;
|
|
25
25
|
|
|
26
|
+
/* The same door in the other direction: where the extension asks for a sandbox account's session to be LENT to
|
|
27
|
+
* this browser, so the owner can finish a step no remote browser can do — a passkey, a hardware second factor,
|
|
28
|
+
* an employer's SSO that checks the device.
|
|
29
|
+
*
|
|
30
|
+
* A separate path rather than a verb on the one above, because the two are different acts with different
|
|
31
|
+
* refusals ("no such account" versus "that account is not signed in there") and a single endpoint doing both
|
|
32
|
+
* would have to be read twice to know which. Same bearer, and the same rule about the socket: the cookies come
|
|
33
|
+
* back on THIS request, never as an MCP result. */
|
|
34
|
+
export const webextLendUrl = (sandboxUrl: string): string => `${sandboxUrl.replace(/\/$/, "")}/system/webext/lend`;
|
|
35
|
+
|
|
26
36
|
/* ---- the pairing code: the one string that travels from the sandbox's card into the extension ----
|
|
27
37
|
*
|
|
28
38
|
* A connected computer is paired by a shell one-liner, which can carry two values in two environment variables
|
|
@@ -162,7 +162,9 @@ describe(`WORKSPACE_STATE_FILES`, () => {
|
|
|
162
162
|
// the difference visible at review time.
|
|
163
163
|
for (const file of WORKSPACE_STATE_FILES) {
|
|
164
164
|
if (file.invalidates.length === 0) {
|
|
165
|
-
|
|
165
|
+
// A string with something in it. Truthiness would accept the number 1 or an object, and the
|
|
166
|
+
// failure this guards against — a `why` left blank to quiet the check — reads as whitespace.
|
|
167
|
+
expect(file.why, `${file.path} invalidates nothing and must say why`).toEqual(expect.stringMatching(/\S/));
|
|
166
168
|
} else {
|
|
167
169
|
expect(file.why, `${file.path} invalidates queries, so \`why\` is dead weight`).toBeUndefined();
|
|
168
170
|
}
|
package/src/workspace-state.ts
CHANGED
|
@@ -301,6 +301,24 @@ const STATE_FILES = [
|
|
|
301
301
|
why: "Declared by the intentic.automations extension's contributes.files, `automation-approvals` is its query key, not core's.",
|
|
302
302
|
portability: "carry",
|
|
303
303
|
},
|
|
304
|
+
/* The bug-report inbox, one file per fingerprint, written by the daemon's issues-store as reports arrive
|
|
305
|
+
* from the owner's own sites and apps, and rendered by the intentic.issues extension. Its invalidation is
|
|
306
|
+
* the extension's own (`issues`), declared in that manifest, the automations shape exactly.
|
|
307
|
+
*
|
|
308
|
+
* `carry` rather than `local`: an issue is a fact about the PRODUCT, not about this container. A workspace
|
|
309
|
+
* exported and restored elsewhere that arrived claiming nothing had ever crashed would have thrown away
|
|
310
|
+
* the one record that says which bug is worth fixing first, and the counts are the whole of that record.
|
|
311
|
+
*
|
|
312
|
+
* NOT `versioned`, and the drafts entry is the contrast worth reading: a draft is authored, reviewable and
|
|
313
|
+
* goes out under the owner's name, so it earns a diff. An issue is machine-recorded telemetry whose count
|
|
314
|
+
* moves on every crash: tracking it would put a commit's worth of churn in `git log` per bad afternoon,
|
|
315
|
+
* and nothing in it is a decision anybody made. */
|
|
316
|
+
{
|
|
317
|
+
path: ".intentic/records/issues/",
|
|
318
|
+
invalidates: [],
|
|
319
|
+
why: "Declared by the intentic.issues extension's contributes.files, `issues` is its query key, not core's.",
|
|
320
|
+
portability: "carry",
|
|
321
|
+
},
|
|
304
322
|
/* The maintenance ledger and probe evidence, written by the daemon's chores-store and rendered by the
|
|
305
323
|
* intentic.maintenance extension, the automations shape exactly: the path is the daemon's, the query keys
|
|
306
324
|
* (`maintenance-report`, `maintenance-runs`) are the extension's own contributes.files. Point-in-time
|
|
@@ -359,6 +377,12 @@ const STATE_FILES = [
|
|
|
359
377
|
why: "Which origins have loaded a Front Desk's widget, written on a 30s flush timer while a customer's site serves page views. The install panel that renders it fetches on open and polls itself while it is on screen, which is the whole window in which the answer changes for anyone. Pushing instead would bill every connected browser a refetch per flush, for a panel almost nobody has open.",
|
|
360
378
|
portability: "carry",
|
|
361
379
|
},
|
|
380
|
+
{
|
|
381
|
+
path: ".intentic/records/issue-installs.json",
|
|
382
|
+
invalidates: [],
|
|
383
|
+
why: "The same probe for the bug reporter's script, on the same flush timer and read by the same kind of panel, so it is outside the push path for the same reason the Front Desk's is.",
|
|
384
|
+
portability: "carry",
|
|
385
|
+
},
|
|
362
386
|
{
|
|
363
387
|
path: ".intentic/records/thread-sessions.json",
|
|
364
388
|
invalidates: [],
|
|
@@ -805,34 +829,6 @@ export const REPORTED_MANIFEST_PATHS: readonly string[] = WORKSPACE_STATE_FILES.
|
|
|
805
829
|
// them relative at the last moment, and normalizing at each call site is the one that eventually gets forgotten.
|
|
806
830
|
export const isReportedManifest = (relPath: string): boolean => REPORTED_MANIFEST_PATHS.includes(relPath.replaceAll("\\", "/"));
|
|
807
831
|
|
|
808
|
-
/* Old directory names are never read or migrated. Keep that finite set in one quarantine record so access,
|
|
809
|
-
* export, and search cannot reinterpret abandoned machine state as ordinary workspace content after a rename.
|
|
810
|
-
* `artifacts` still carry; the distinction here tells portability only which retired roots are secrets or
|
|
811
|
-
* derived, and tells the state janitor which it may DELETE: a retired `derived` root is a rebuildable cache
|
|
812
|
-
* by its own classification, so leaving 466 MB of abandoned model where only a manual `rm` reaches it was
|
|
813
|
-
* quarantine doing half its job. Secret and artifact roots stay until an owner removes them by hand: deleting
|
|
814
|
-
* content is not the janitor's call, only deleting what the class already says is disposable. */
|
|
815
|
-
export const RETIRED_WORKSPACE_STATE_DIRS = {
|
|
816
|
-
/* The flat spellings, from before the state dir was grouped into its five folders. Every entry in the table
|
|
817
|
-
* used to sit directly under `.intentic/`, and a sandbox that predates the move still has those directories
|
|
818
|
-
* on disk with real contents in them. They are quarantined rather than migrated, the workspace rule is fresh
|
|
819
|
-
* state and no compatibility layers, but quarantine is exactly what they need, because the alternative is
|
|
820
|
-
* worse than leaving them: `auth` at the old spelling is a real credential store that the classifier no
|
|
821
|
-
* longer recognises, so without these names it would read as ordinary workspace content and be indexed by
|
|
822
|
-
* search, carried by an export and copied down by the backup. Being listed here keeps the old copy as
|
|
823
|
-
* untouchable as the new one, and lets the janitor delete the rebuildable half. */
|
|
824
|
-
secret: ["claude", "codex", "kimi", "opencode", "cliproxy", "auth", "ci.json"],
|
|
825
|
-
/* The ownership records at their OLD flat spelling, and a bucket of their own rather than a few more names
|
|
826
|
-
* in `secret`, because the two classes differ on exactly the thing that matters for a leftover. A `secret`
|
|
827
|
-
* travels when the owner opts in at export; an `identity` never travels at all, since a list of who may
|
|
828
|
-
* drive this sandbox arriving in another one is how a source hands itself the target's ownership. Filed under
|
|
829
|
-
* `secret` these would have become carryable by ticking a box, which is the one outcome their live entries
|
|
830
|
-
* are written to prevent. */
|
|
831
|
-
identity: ["owner.json", "members.json", "workspace.json", "control-tokens.json"],
|
|
832
|
-
derived: ["iq", "extensions-runtime", "whisper", "browser/output", "browser", "cache", "runtime", "tmp", ".pnpm-store", "extensions", "verify"],
|
|
833
|
-
artifacts: ["attachments", "acceptance", "loops", "workflow-runs", "transcripts", "artifacts", "sessions"],
|
|
834
|
-
} as const;
|
|
835
|
-
|
|
836
832
|
/* THE DAEMON'S OWN CONTROL PLANE, the entries directly under the workspace root's `.intentic/` that the file
|
|
837
833
|
* API refuses to read, write, move or delete for anyone, the owner included (workspace/workspace-files.ts holds
|
|
838
834
|
* the enforcement and the full reasoning for each name).
|
|
@@ -884,15 +880,9 @@ export const isLockedWorkspacePath = (relPath: string): boolean => {
|
|
|
884
880
|
if (segments[0] !== STATE_DIR) {
|
|
885
881
|
return false;
|
|
886
882
|
}
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
* live one, and the whole point of quarantining the old spellings was that nothing downstream reclassifies
|
|
891
|
-
* them as ordinary content. */
|
|
892
|
-
return (
|
|
893
|
-
LOCKED_STATE_ENTRIES.has(segments.slice(1, 3).join("/")) ||
|
|
894
|
-
(segments.length >= 2 && RETIRED_WORKSPACE_STATE_DIRS.secret.includes(segments[1] as never))
|
|
895
|
-
);
|
|
883
|
+
// Two segments, which covers both spellings in the set above: a grouped entry (`secrets/auth`) matches as
|
|
884
|
+
// written, and a bare root entry (`claude.json`) joins to itself because there is no second segment to add.
|
|
885
|
+
return LOCKED_STATE_ENTRIES.has(segments.slice(1, 3).join("/"));
|
|
896
886
|
};
|
|
897
887
|
|
|
898
888
|
/* THE LOCKED ENTRIES THE ROOT REPO TRACKS, refused by the file API, and diffable anyway.
|