@intentic/sandbox-contract 1.312.0 → 1.314.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 +26 -3
- package/dist/chores/index.d.ts +0 -1
- package/dist/chores/index.d.ts.map +1 -1
- package/dist/chores/index.js +0 -1
- package/dist/chores/index.js.map +1 -1
- package/dist/contracts/accounts.contract.d.ts +57 -0
- package/dist/contracts/accounts.contract.d.ts.map +1 -1
- package/dist/contracts/agent.contract.d.ts +436 -0
- package/dist/contracts/agent.contract.d.ts.map +1 -1
- package/dist/contracts/agent.contract.js +11 -1
- package/dist/contracts/agent.contract.js.map +1 -1
- package/dist/contracts/agents.contract.d.ts +123 -96
- package/dist/contracts/agents.contract.d.ts.map +1 -1
- package/dist/contracts/capabilities.contract.js +1 -1
- package/dist/contracts/capabilities.contract.js.map +1 -1
- package/dist/contracts/device.contract.d.ts +20 -4
- package/dist/contracts/device.contract.d.ts.map +1 -1
- package/dist/contracts/device.contract.js +1 -1
- package/dist/contracts/device.contract.js.map +1 -1
- package/dist/contracts/extensions.contract.d.ts +11 -0
- package/dist/contracts/extensions.contract.d.ts.map +1 -1
- package/dist/contracts/git.contract.d.ts +2 -0
- package/dist/contracts/git.contract.d.ts.map +1 -1
- package/dist/contracts/personas.contract.d.ts +2 -0
- package/dist/contracts/personas.contract.d.ts.map +1 -1
- package/dist/contracts/runner.contract.d.ts +138 -0
- package/dist/contracts/runner.contract.d.ts.map +1 -1
- package/dist/contracts/secrets.contract.d.ts.map +1 -1
- package/dist/contracts/secrets.contract.js +1 -0
- package/dist/contracts/secrets.contract.js.map +1 -1
- package/dist/contracts/sessions.contract.d.ts +59 -0
- package/dist/contracts/sessions.contract.d.ts.map +1 -1
- package/dist/contracts/settings.contract.d.ts +12 -8
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/contracts/settings.contract.js +2 -2
- package/dist/contracts/settings.contract.js.map +1 -1
- package/dist/contracts/system.contract.d.ts +141 -19
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/contracts/translator.contract.d.ts +72 -0
- package/dist/contracts/translator.contract.d.ts.map +1 -1
- package/dist/contracts/workspace.contract.d.ts +85 -14
- package/dist/contracts/workspace.contract.d.ts.map +1 -1
- package/dist/documents/index.d.ts +1 -0
- package/dist/documents/index.d.ts.map +1 -1
- package/dist/documents/index.js +1 -0
- package/dist/documents/index.js.map +1 -1
- package/dist/documents/read.d.ts +24 -0
- package/dist/documents/read.d.ts.map +1 -0
- package/dist/documents/read.js +81 -0
- package/dist/documents/read.js.map +1 -0
- package/dist/events/agent-events.d.ts +235 -0
- package/dist/events/agent-events.d.ts.map +1 -1
- package/dist/events/agent-events.js +3 -0
- package/dist/events/agent-events.js.map +1 -1
- package/dist/events/child-run.d.ts +9 -0
- package/dist/events/child-run.d.ts.map +1 -0
- package/dist/events/child-run.js +41 -0
- package/dist/events/child-run.js.map +1 -0
- package/dist/events/errands.d.ts +5 -0
- package/dist/events/errands.d.ts.map +1 -0
- package/dist/events/errands.js +19 -0
- package/dist/events/errands.js.map +1 -0
- package/dist/events/requests.d.ts +152 -0
- package/dist/events/requests.d.ts.map +1 -1
- package/dist/events/requests.js +26 -1
- package/dist/events/requests.js.map +1 -1
- package/dist/events/system-events.d.ts +8 -12
- package/dist/events/system-events.d.ts.map +1 -1
- package/dist/events/transcript.d.ts +387 -0
- package/dist/events/transcript.d.ts.map +1 -1
- package/dist/events/transcript.js +3 -0
- package/dist/events/transcript.js.map +1 -1
- package/dist/front/browser-wire.d.ts +6 -0
- package/dist/front/browser-wire.d.ts.map +1 -0
- package/dist/front/browser-wire.js +4 -0
- package/dist/front/browser-wire.js.map +1 -0
- package/dist/front/front-wire.d.ts +6 -0
- package/dist/front/front-wire.d.ts.map +1 -0
- package/dist/front/front-wire.js +5 -0
- package/dist/front/front-wire.js.map +1 -0
- package/dist/front/generated/browser-wire.d.ts +19 -0
- package/dist/front/generated/browser-wire.d.ts.map +1 -0
- package/dist/front/generated/browser-wire.js +2 -0
- package/dist/front/generated/browser-wire.js.map +1 -0
- package/dist/front/generated/wire.d.ts +36 -30
- package/dist/front/generated/wire.d.ts.map +1 -1
- package/dist/ids/conversation-ids.d.ts +1 -0
- package/dist/ids/conversation-ids.d.ts.map +1 -1
- package/dist/ids/conversation-ids.js +2 -2
- package/dist/ids/conversation-ids.js.map +1 -1
- package/dist/ids/session-names.d.ts +3 -0
- package/dist/ids/session-names.d.ts.map +1 -1
- package/dist/ids/session-names.js +3 -0
- package/dist/ids/session-names.js.map +1 -1
- package/dist/index.d.ts +1038 -168
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/models/agent-catalog.d.ts.map +1 -1
- package/dist/models/agent-catalog.js +4 -6
- package/dist/models/agent-catalog.js.map +1 -1
- package/dist/models/agent-runtimes.d.ts +2 -1
- package/dist/models/agent-runtimes.d.ts.map +1 -1
- package/dist/models/agent-runtimes.js +7 -1
- package/dist/models/agent-runtimes.js.map +1 -1
- package/dist/models/plan-pools.d.ts +22 -1
- package/dist/models/plan-pools.d.ts.map +1 -1
- package/dist/models/plan-pools.js +86 -0
- package/dist/models/plan-pools.js.map +1 -1
- package/dist/policy/request-status.d.ts +1 -1
- package/dist/policy/request-status.d.ts.map +1 -1
- package/dist/policy/request-status.js +4 -11
- package/dist/policy/request-status.js.map +1 -1
- package/dist/policy/reserved-servers.d.ts.map +1 -1
- package/dist/policy/reserved-servers.js +2 -1
- package/dist/policy/reserved-servers.js.map +1 -1
- package/dist/policy/turned-away.d.ts.map +1 -1
- package/dist/policy/turned-away.js +1 -0
- package/dist/policy/turned-away.js.map +1 -1
- package/dist/protocol/edge-verdict.d.ts +2 -1
- package/dist/protocol/edge-verdict.d.ts.map +1 -1
- package/dist/protocol/edge-verdict.js +1 -1
- package/dist/protocol/edge-verdict.js.map +1 -1
- package/dist/protocol/ingress-contract.d.ts +1 -1
- package/dist/protocol/ingress-contract.d.ts.map +1 -1
- package/dist/protocol/ingress-contract.js +1 -1
- package/dist/protocol/ingress-contract.js.map +1 -1
- package/dist/protocol/raw-routes.d.ts +18 -40
- package/dist/protocol/raw-routes.d.ts.map +1 -1
- package/dist/protocol/raw-routes.js +5 -16
- package/dist/protocol/raw-routes.js.map +1 -1
- package/dist/protocol/route-meta.d.ts +20 -0
- package/dist/protocol/route-meta.d.ts.map +1 -1
- package/dist/protocol/route-meta.js +16 -0
- package/dist/protocol/route-meta.js.map +1 -1
- package/dist/protocol/tunnel-bulk.d.ts +2 -0
- package/dist/protocol/tunnel-bulk.d.ts.map +1 -0
- package/dist/protocol/tunnel-bulk.js +3 -0
- package/dist/protocol/tunnel-bulk.js.map +1 -0
- package/dist/protocol/x402.d.ts +17 -0
- package/dist/protocol/x402.d.ts.map +1 -0
- package/dist/protocol/x402.js +37 -0
- package/dist/protocol/x402.js.map +1 -0
- package/dist/schemas/agent.d.ts +25 -0
- package/dist/schemas/agent.d.ts.map +1 -1
- package/dist/schemas/agent.js +14 -2
- package/dist/schemas/agent.js.map +1 -1
- package/dist/schemas/agents.d.ts +17 -18
- package/dist/schemas/agents.d.ts.map +1 -1
- package/dist/schemas/agents.js +17 -4
- package/dist/schemas/agents.js.map +1 -1
- package/dist/schemas/automations.d.ts +4 -6
- package/dist/schemas/automations.d.ts.map +1 -1
- package/dist/schemas/automations.js.map +1 -1
- package/dist/schemas/devices.d.ts +134 -7
- package/dist/schemas/devices.d.ts.map +1 -1
- package/dist/schemas/devices.js +43 -18
- package/dist/schemas/devices.js.map +1 -1
- package/dist/schemas/extension-updates.d.ts +22 -0
- package/dist/schemas/extension-updates.d.ts.map +1 -1
- package/dist/schemas/extension-updates.js +4 -0
- package/dist/schemas/extension-updates.js.map +1 -1
- package/dist/schemas/git/git.d.ts +3 -0
- package/dist/schemas/git/git.d.ts.map +1 -1
- package/dist/schemas/hosts.d.ts +35 -5
- package/dist/schemas/hosts.d.ts.map +1 -1
- package/dist/schemas/hosts.js +21 -7
- package/dist/schemas/hosts.js.map +1 -1
- package/dist/schemas/keep-warm.d.ts +2 -12
- package/dist/schemas/keep-warm.d.ts.map +1 -1
- package/dist/schemas/keep-warm.js +8 -10
- package/dist/schemas/keep-warm.js.map +1 -1
- package/dist/schemas/metrics.d.ts +24 -0
- package/dist/schemas/metrics.d.ts.map +1 -1
- package/dist/schemas/metrics.js +22 -3
- package/dist/schemas/metrics.js.map +1 -1
- package/dist/schemas/personas.d.ts +3 -0
- package/dist/schemas/personas.d.ts.map +1 -1
- package/dist/schemas/personas.js +9 -0
- package/dist/schemas/personas.js.map +1 -1
- package/dist/schemas/providers/plan-limits.d.ts +140 -0
- package/dist/schemas/providers/plan-limits.d.ts.map +1 -1
- package/dist/schemas/providers/plan-limits.js +47 -1
- package/dist/schemas/providers/plan-limits.js.map +1 -1
- package/dist/schemas/providers/provider-oauth.d.ts +57 -0
- package/dist/schemas/providers/provider-oauth.d.ts.map +1 -1
- package/dist/schemas/providers/provider-oauth.js +6 -1
- package/dist/schemas/providers/provider-oauth.js.map +1 -1
- package/dist/schemas/settings-history.d.ts +28 -1
- package/dist/schemas/settings-history.d.ts.map +1 -1
- package/dist/schemas/settings-history.js +35 -1
- package/dist/schemas/settings-history.js.map +1 -1
- package/dist/schemas/settings.d.ts +184 -4
- package/dist/schemas/settings.d.ts.map +1 -1
- package/dist/schemas/settings.js +31 -23
- package/dist/schemas/settings.js.map +1 -1
- package/dist/schemas/speaker.d.ts +36 -0
- package/dist/schemas/speaker.d.ts.map +1 -0
- package/dist/schemas/speaker.js +62 -0
- package/dist/schemas/speaker.js.map +1 -0
- package/dist/schemas/state-plan.d.ts +46 -0
- package/dist/schemas/state-plan.d.ts.map +1 -0
- package/dist/schemas/state-plan.js +31 -0
- package/dist/schemas/state-plan.js.map +1 -0
- package/dist/schemas/system.d.ts +48 -8
- package/dist/schemas/system.d.ts.map +1 -1
- package/dist/schemas/system.js +10 -14
- package/dist/schemas/system.js.map +1 -1
- package/dist/schemas/workspace/mainline.d.ts +206 -64
- package/dist/schemas/workspace/mainline.d.ts.map +1 -1
- package/dist/schemas/workspace/mainline.js +73 -25
- package/dist/schemas/workspace/mainline.js.map +1 -1
- package/dist/state/contract-lock.d.ts +1 -0
- package/dist/state/contract-lock.d.ts.map +1 -1
- package/dist/state/contract-lock.js +47 -1
- package/dist/state/contract-lock.js.map +1 -1
- package/dist/state/definition.d.ts +24 -16
- package/dist/state/definition.d.ts.map +1 -1
- package/dist/state/fix-stance.d.ts +1 -1
- package/dist/state/fix-stance.d.ts.map +1 -1
- package/dist/state/fix-stance.js +2 -1
- package/dist/state/fix-stance.js.map +1 -1
- package/dist/state/history-state.d.ts.map +1 -1
- package/dist/state/history-state.js +2 -0
- package/dist/state/history-state.js.map +1 -1
- package/dist/state/landed-commit.d.ts +1 -0
- package/dist/state/landed-commit.d.ts.map +1 -1
- package/dist/state/landed-commit.js +2 -0
- package/dist/state/landed-commit.js.map +1 -1
- package/dist/state/red-streak.d.ts +7 -0
- package/dist/state/red-streak.d.ts.map +1 -0
- package/dist/state/red-streak.js +11 -0
- package/dist/state/red-streak.js.map +1 -0
- package/dist/state/workspace-state.d.ts +1 -0
- package/dist/state/workspace-state.d.ts.map +1 -1
- package/dist/state/workspace-state.js +6 -2
- package/dist/state/workspace-state.js.map +1 -1
- package/dist/text/shell-regions.d.ts +1 -0
- package/dist/text/shell-regions.d.ts.map +1 -1
- package/dist/text/shell-regions.js +1 -1
- package/dist/text/shell-regions.js.map +1 -1
- package/dist/text/transcript-fold.d.ts.map +1 -1
- package/dist/text/transcript-fold.js +14 -7
- package/dist/text/transcript-fold.js.map +1 -1
- package/package.json +28 -29
- package/src/chores/index.ts +0 -1
- package/src/contracts/agent.contract.ts +14 -0
- package/src/contracts/capabilities.contract.ts +2 -2
- package/src/contracts/device.contract.ts +5 -3
- package/src/contracts/secrets.contract.ts +2 -0
- package/src/contracts/settings.contract.ts +2 -1
- package/src/documents/conversions.ts +3 -3
- package/src/documents/index.ts +3 -1
- package/src/documents/read.ts +114 -0
- package/src/events/agent-events.ts +5 -0
- package/src/events/child-run.test.ts +81 -0
- package/src/events/child-run.ts +73 -0
- package/src/events/errands.test.ts +22 -0
- package/src/events/errands.ts +27 -0
- package/src/events/requests.ts +40 -1
- package/src/events/transcript.ts +8 -0
- package/src/front/browser-wire.ts +20 -0
- package/src/front/front-wire.ts +15 -0
- package/src/front/generated/browser-wire.json +138 -0
- package/src/front/generated/browser-wire.ts +16 -0
- package/src/front/generated/front-wire.json +793 -0
- package/src/front/generated/tunnel.json +38 -0
- package/src/front/generated/wire.ts +21 -24
- package/src/front/wire-manifests.test.ts +61 -0
- package/src/ids/conversation-ids.ts +6 -5
- package/src/ids/session-names.ts +7 -0
- package/src/index.ts +8 -1
- package/src/models/agent-catalog.test.ts +6 -4
- package/src/models/agent-catalog.ts +4 -6
- package/src/models/agent-runtimes.ts +22 -7
- package/src/models/capability-ledger.test.ts +2 -0
- package/src/models/plan-pools.test.ts +121 -2
- package/src/models/plan-pools.ts +152 -2
- package/src/policy/request-status.ts +4 -12
- package/src/policy/reserved-servers.test.ts +6 -0
- package/src/policy/reserved-servers.ts +13 -10
- package/src/policy/turned-away.ts +3 -0
- package/src/protocol/edge-verdict.ts +13 -11
- package/src/protocol/ingress-contract.fixture.json +13 -0
- package/src/protocol/ingress-contract.ts +9 -5
- package/src/protocol/raw-routes.ts +10 -21
- package/src/protocol/route-meta.ts +36 -1
- package/src/protocol/tunnel-bulk.test.ts +37 -0
- package/src/protocol/tunnel-bulk.ts +7 -0
- package/src/protocol/x402.test.ts +29 -0
- package/src/protocol/x402.ts +61 -0
- package/src/schemas/agent.ts +28 -3
- package/src/schemas/agents.ts +33 -7
- package/src/schemas/automations.ts +4 -2
- package/src/schemas/devices.ts +107 -39
- package/src/schemas/extension-updates.ts +6 -0
- package/src/schemas/hosts.ts +68 -19
- package/src/schemas/ic-shape-args.test.ts +64 -0
- package/src/schemas/keep-warm.test.ts +6 -13
- package/src/schemas/keep-warm.ts +15 -20
- package/src/schemas/metrics.ts +32 -3
- package/src/schemas/personas.ts +15 -0
- package/src/schemas/providers/plan-limits.ts +71 -1
- package/src/schemas/providers/provider-oauth.ts +10 -1
- package/src/schemas/settings-history.ts +50 -3
- package/src/schemas/settings.test.ts +54 -1
- package/src/schemas/settings.ts +49 -31
- package/src/schemas/speaker.ts +89 -0
- package/src/schemas/state-plan.test.ts +69 -0
- package/src/schemas/state-plan.ts +47 -0
- package/src/schemas/system.ts +20 -16
- package/src/schemas/workspace/mainline.test.ts +28 -0
- package/src/schemas/workspace/mainline.ts +111 -44
- package/src/state/contract-lock.test.ts +65 -4
- package/src/state/contract-lock.ts +70 -2
- package/src/state/fix-stance.test.ts +8 -0
- package/src/state/fix-stance.ts +3 -3
- package/src/state/history-state.ts +5 -1
- package/src/state/landed-commit.test.ts +12 -1
- package/src/state/landed-commit.ts +5 -0
- package/src/state/red-streak.test.ts +28 -0
- package/src/state/red-streak.ts +28 -0
- package/src/state/workspace-state.test.ts +7 -1
- package/src/state/workspace-state.ts +10 -2
- package/src/text/shell-regions.test.ts +38 -0
- package/src/text/shell-regions.ts +3 -3
- package/src/text/transcript-fold.test.ts +13 -0
- package/src/text/transcript-fold.ts +17 -8
- package/dist/chores/fix-deps.d.ts +0 -10
- package/dist/chores/fix-deps.d.ts.map +0 -1
- package/dist/chores/fix-deps.js +0 -19
- package/dist/chores/fix-deps.js.map +0 -1
- package/dist/front/terminal-frames.d.ts +0 -17
- package/dist/front/terminal-frames.d.ts.map +0 -1
- package/dist/front/terminal-frames.js +0 -79
- package/dist/front/terminal-frames.js.map +0 -1
- package/dist/protocol/tunnel-lanes.d.ts +0 -10
- package/dist/protocol/tunnel-lanes.d.ts.map +0 -1
- package/dist/protocol/tunnel-lanes.js +0 -16
- package/dist/protocol/tunnel-lanes.js.map +0 -1
- package/dist/protocol/upgrade-relay.d.ts +0 -4
- package/dist/protocol/upgrade-relay.d.ts.map +0 -1
- package/dist/protocol/upgrade-relay.js +0 -25
- package/dist/protocol/upgrade-relay.js.map +0 -1
- package/src/chores/fix-deps.ts +0 -23
- package/src/front/terminal-frames.fixture.json +0 -55
- package/src/front/terminal-frames.test.ts +0 -63
- package/src/front/terminal-frames.ts +0 -102
- package/src/protocol/tunnel-lanes.json +0 -136
- package/src/protocol/tunnel-lanes.test.ts +0 -30
- package/src/protocol/tunnel-lanes.ts +0 -32
- package/src/protocol/upgrade-relay.ts +0 -28
|
@@ -14,6 +14,20 @@ export const MainlineLandSchema = z.object({
|
|
|
14
14
|
});
|
|
15
15
|
export type MainlineLand = z.infer<typeof MainlineLandSchema>;
|
|
16
16
|
|
|
17
|
+
// A land a red is laid at, as a reader names it.
|
|
18
|
+
export const MainlineLandRefSchema = z.object({
|
|
19
|
+
conversationId: z.string().describe("The conversation whose work landed."),
|
|
20
|
+
title: z.string().optional().describe("Its title when it landed."),
|
|
21
|
+
});
|
|
22
|
+
export type MainlineLandRef = z.infer<typeof MainlineLandRefSchema>;
|
|
23
|
+
|
|
24
|
+
// One failure a run named, split the way a reader scans it.
|
|
25
|
+
export const MainlineFailureSchema = z.object({
|
|
26
|
+
name: z.string().describe("What failed: a test by its own name, anything else (a type error, a whole task) as the check printed it."),
|
|
27
|
+
path: z.string().optional().describe("The repository path it failed in, when it names one."),
|
|
28
|
+
});
|
|
29
|
+
export type MainlineFailure = z.infer<typeof MainlineFailureSchema>;
|
|
30
|
+
|
|
17
31
|
// What happened to a red verdict's failures, decided once the lands behind it had their own check.
|
|
18
32
|
export const MainlineRoutingKindSchema = z.enum([
|
|
19
33
|
// More work landed while this ran; the check that measures it decides, so nobody is sent after a failure it may fix.
|
|
@@ -30,6 +44,8 @@ export const MainlineRoutingKindSchema = z.enum([
|
|
|
30
44
|
"resolved",
|
|
31
45
|
// The sends and fresh attempts a red streak allows are used up; it waits for a person.
|
|
32
46
|
"spent",
|
|
47
|
+
// A person set findings aside as not to be fixed (`findings` names which); what a push left is the owner's to dismiss.
|
|
48
|
+
"dismissed",
|
|
33
49
|
]);
|
|
34
50
|
export type MainlineRoutingKind = z.infer<typeof MainlineRoutingKindSchema>;
|
|
35
51
|
|
|
@@ -41,6 +57,10 @@ export const MainlineRoutingSchema = z.object({
|
|
|
41
57
|
.describe("The conversation working on them (the original or a fresh one), or the one still working that the repair waits for."),
|
|
42
58
|
at: z.number().describe("When that was decided, in milliseconds."),
|
|
43
59
|
detail: z.string().optional().describe("One sentence on why, in the sandbox's words."),
|
|
60
|
+
findings: z
|
|
61
|
+
.array(z.string())
|
|
62
|
+
.optional()
|
|
63
|
+
.describe("The findings it was about, by id, when it was about some of a red's findings rather than the whole red."),
|
|
44
64
|
});
|
|
45
65
|
export type MainlineRouting = z.infer<typeof MainlineRoutingSchema>;
|
|
46
66
|
|
|
@@ -61,6 +81,16 @@ export const MainlineRunSchema = z.object({
|
|
|
61
81
|
.array(z.string())
|
|
62
82
|
.optional()
|
|
63
83
|
.describe("The conversations whose lands these failures were laid at, when any could be named."),
|
|
84
|
+
named: z
|
|
85
|
+
.boolean()
|
|
86
|
+
.optional()
|
|
87
|
+
.describe(
|
|
88
|
+
"Whether the failures were laid at the suspects by the paths they changed. False when nobody could be told apart (every land the run covered is a suspect) or nothing new failed in it (none is). Absent on green, and from a daemon that did not say.",
|
|
89
|
+
),
|
|
90
|
+
units: z
|
|
91
|
+
.array(MainlineFailureSchema)
|
|
92
|
+
.optional()
|
|
93
|
+
.describe("The same first failures, split into a name and the path they failed in. Absent from a daemon that does not split them."),
|
|
64
94
|
routing: MainlineRoutingSchema.optional().describe("What became of a red run's failures. Absent on green, and until it is decided."),
|
|
65
95
|
});
|
|
66
96
|
export type MainlineRun = z.infer<typeof MainlineRunSchema>;
|
|
@@ -76,53 +106,82 @@ export const MainlineProjectSchema = z.object({
|
|
|
76
106
|
})
|
|
77
107
|
.optional()
|
|
78
108
|
.describe("The check running on it now, if any."),
|
|
109
|
+
session: z
|
|
110
|
+
.string()
|
|
111
|
+
.optional()
|
|
112
|
+
.describe("The terminal session its check runs in, running or last, to attach to for the whole log. Absent from a daemon that does not say."),
|
|
79
113
|
queued: z.array(MainlineLandSchema).describe("Lands waiting for the next check, which will measure them together."),
|
|
80
114
|
last: MainlineRunSchema.optional().describe("Its most recent settled check."),
|
|
81
115
|
redSince: z.number().optional().describe("When its checks went red and stayed so, in milliseconds. Absent while green."),
|
|
116
|
+
red: z
|
|
117
|
+
.object({
|
|
118
|
+
since: z.number().describe("When its checks went red and stayed so, in milliseconds."),
|
|
119
|
+
cause: z
|
|
120
|
+
.array(MainlineLandRefSchema)
|
|
121
|
+
.describe("The work this red is laid at, oldest first: the lands its failures were laid at on the latest run of the streak that could name any. Empty when none could be."),
|
|
122
|
+
named: z.boolean().describe("Whether `cause` was narrowed by the paths the lands changed; false when it is every land the run covered."),
|
|
123
|
+
fixer: MainlineRoutingSchema.optional().describe("The latest decision about who has it. Absent until one is made."),
|
|
124
|
+
})
|
|
125
|
+
.optional()
|
|
126
|
+
.describe("The red streak it is in, as the sandbox laid it. Absent while green, and from a daemon that does not lay it."),
|
|
82
127
|
});
|
|
83
128
|
export type MainlineProject = z.infer<typeof MainlineProjectSchema>;
|
|
84
129
|
|
|
85
130
|
|
|
86
131
|
// WHAT A PUSH LEFT BEHIND. The pre-push hook never refuses (verify-push.mjs, `--advisory`): it measures, reports, and the
|
|
87
132
|
// push goes. What it found used to vanish with the terminal it printed to. It now leaves a report in the repository's
|
|
88
|
-
// git dir, the daemon files it
|
|
89
|
-
// measurement no longer prints it or somebody dismisses it. Nobody is sent after
|
|
90
|
-
|
|
91
|
-
// What measured a finding: one of the repository's checks (by id), the linter, the assertion ratchet, the
|
|
92
|
-
// manifest/lockfile lockstep, or rustfmt. Only a check's or the linter's finding can be measured again; the other three
|
|
93
|
-
// are about the pushed commits themselves, so they end only when dismissed.
|
|
94
|
-
export const PushFindingKindSchema = z.enum(["check", "lint", "ratchet", "lockstep", "rustfmt"]);
|
|
95
|
-
export type PushFindingKind = z.infer<typeof PushFindingKindSchema>;
|
|
96
|
-
|
|
97
|
-
export const PushFindingStateSchema = z.enum([
|
|
98
|
-
// Still printed at the last measurement, and nobody has dismissed it.
|
|
99
|
-
"open",
|
|
100
|
-
// A later push, recheck or land check measured it and it was gone.
|
|
101
|
-
"resolved",
|
|
102
|
-
// Somebody decided it will not be fixed.
|
|
103
|
-
"dismissed",
|
|
104
|
-
]);
|
|
105
|
-
export type PushFindingState = z.infer<typeof PushFindingStateSchema>;
|
|
133
|
+
// git dir, the daemon files it once the push has actually reached the remote, and what it found is owed, as the
|
|
134
|
+
// project's push Red, until a later measurement no longer prints it or somebody dismisses it. Nobody is sent after it:
|
|
135
|
+
// acting on it is the owner's call (RED_POLICY).
|
|
106
136
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
137
|
+
// ONE FINDING, whatever measured it: a check run's line as the repository's own tooling printed it, named by what
|
|
138
|
+
// printed it (`source`, the repository's word: a check's id, `lint`, a hook), with the command that shows it again and
|
|
139
|
+
// whether a later measurement can say it is gone. A push's findings are these; a land check's failures and a CI run's
|
|
140
|
+
// failed jobs become them too.
|
|
141
|
+
export const FindingSchema = z.object({
|
|
142
|
+
id: z.string().describe("Stable across measurements: the same problem found again is the same id."),
|
|
143
|
+
source: z.string().describe("What measured it, in the repository's own words: a check's id, `lint`, a hook."),
|
|
144
|
+
text: z.string().describe("The finding as it was printed."),
|
|
145
|
+
path: z.string().optional().describe("The repository path it is about, when it names one."),
|
|
146
|
+
command: z.string().optional().describe("The command that shows it again."),
|
|
147
|
+
recheckable: z
|
|
148
|
+
.boolean()
|
|
149
|
+
.describe("Whether a later measurement can find it gone. False for one about commits already made, which ends only when dismissed."),
|
|
111
150
|
gate: z
|
|
112
151
|
.enum(["code", "tidy"])
|
|
113
152
|
.optional()
|
|
114
|
-
.describe("For a check's finding: `code` means the tree fails the check whoever caused it; `tidy` means the
|
|
115
|
-
text: z.string().describe("The finding as the check printed it."),
|
|
116
|
-
command: z.string().optional().describe("The command that shows it again."),
|
|
153
|
+
.describe("For a check's finding: `code` means the tree fails the check whoever caused it; `tidy` means the change measured added this line."),
|
|
117
154
|
commit: z
|
|
118
155
|
.object({ sha: z.string(), subject: z.string() })
|
|
119
156
|
.optional()
|
|
120
|
-
.describe("The newest
|
|
121
|
-
state: PushFindingStateSchema,
|
|
122
|
-
settledAt: z.number().optional().describe("When it was resolved or dismissed, in milliseconds."),
|
|
157
|
+
.describe("The newest commit of the change measured that touched the path it names, when it names one."),
|
|
123
158
|
});
|
|
124
|
-
export type
|
|
159
|
+
export type Finding = z.infer<typeof FindingSchema>;
|
|
160
|
+
|
|
161
|
+
// ONE RED, whatever went red: a land check's project, what a push left in a project, main's CI on a branch. What it owes
|
|
162
|
+
// (its findings), who it was laid at and whether that was narrowed, and every decision about it, oldest first. Each
|
|
163
|
+
// source answers its own red by its own policy (RED_POLICY); the record is the same.
|
|
164
|
+
export const RedSourceSchema = z.enum(["land", "push", "ci"]);
|
|
165
|
+
export type RedSource = z.infer<typeof RedSourceSchema>;
|
|
125
166
|
|
|
167
|
+
// Who answers a red: `route` sends it itself (the land check's router, conversations/land/land-breakage.ts), `owner`
|
|
168
|
+
// waits for a person's press (what a push left, conversations/fix/push-fix.ts), `fix-agent` starts the source's own fix
|
|
169
|
+
// agent once the red is main's standing word (CI, ci/repair-gate.ts).
|
|
170
|
+
export const RED_POLICY = { land: "route", push: "owner", ci: "fix-agent" } as const satisfies Record<RedSource, "route" | "owner" | "fix-agent">;
|
|
171
|
+
|
|
172
|
+
export const RedSchema = z.object({
|
|
173
|
+
source: RedSourceSchema.describe("What went red."),
|
|
174
|
+
scope: z.string().describe("Where: a project's folder, or a repository and branch."),
|
|
175
|
+
since: z.number().describe("When the red streak began (state/red-streak.ts)."),
|
|
176
|
+
findings: z.array(FindingSchema).default([]).describe("What it owes, as the red's own measurement named it."),
|
|
177
|
+
suspects: z.array(z.string()).default([]).describe("The conversations it was laid at."),
|
|
178
|
+
named: z.boolean().default(false).describe("Whether the paths they changed narrowed it to them."),
|
|
179
|
+
decisions: z.array(MainlineRoutingSchema).default([]).describe("What was decided about it, oldest first."),
|
|
180
|
+
});
|
|
181
|
+
export type Red = z.infer<typeof RedSchema>;
|
|
182
|
+
|
|
183
|
+
// ONE PUSH THE HOOK MEASURED, as the record lists it: where it went and what it found that it brought in. What of that is
|
|
184
|
+
// still owed is the project's push Red's to say (`findings`), never the push's.
|
|
126
185
|
export const MainlinePushSchema = z.object({
|
|
127
186
|
project: z.string().describe("Which project, by folder relative to the workspace. Empty is the workspace root."),
|
|
128
187
|
id: z.string().describe("The report's own id."),
|
|
@@ -132,18 +191,29 @@ export const MainlinePushSchema = z.object({
|
|
|
132
191
|
base: z.string().optional().describe("The commit the pushed range starts from; absent when the remote had nothing to compare with."),
|
|
133
192
|
head: z.string().describe("The commit that was pushed."),
|
|
134
193
|
commits: z.number().describe("How many commits the push carried."),
|
|
135
|
-
findings: z
|
|
136
|
-
|
|
194
|
+
findings: z
|
|
195
|
+
.array(FindingSchema)
|
|
196
|
+
.describe("What it found that this push brought in, less what an earlier push had already left open. Empty for a clean push."),
|
|
197
|
+
refused: z
|
|
198
|
+
.boolean()
|
|
199
|
+
.optional()
|
|
200
|
+
.describe("True when the repository's own pre-push hook refused it, so nothing reached the remote and its one finding is what the hook said."),
|
|
137
201
|
});
|
|
138
202
|
export type MainlinePush = z.infer<typeof MainlinePushSchema>;
|
|
139
203
|
|
|
140
204
|
export const MainlineStatusSchema = z.object({
|
|
141
205
|
projects: z.array(MainlineProjectSchema).describe("Every project a land has been checked in, or is waiting to be."),
|
|
142
206
|
recent: z.array(MainlineRunSchema).describe("The latest settled checks across every project, newest first."),
|
|
143
|
-
|
|
207
|
+
pushed: z
|
|
144
208
|
.array(MainlinePushSchema)
|
|
145
209
|
.optional()
|
|
146
|
-
.describe("The latest
|
|
210
|
+
.describe("The latest pushes the hook measured across every project, newest first, with what each brought in. Absent from a daemon that records none."),
|
|
211
|
+
reds: z
|
|
212
|
+
.array(RedSchema)
|
|
213
|
+
.optional()
|
|
214
|
+
.describe(
|
|
215
|
+
"Every red the main line holds, one record whatever went red: each red project's land check and what pushes left in each project, with what each owes and every decision about it. Absent from a daemon that keeps none.",
|
|
216
|
+
),
|
|
147
217
|
});
|
|
148
218
|
export type MainlineStatus = z.infer<typeof MainlineStatusSchema>;
|
|
149
219
|
|
|
@@ -187,15 +257,12 @@ export const MainlinePushFixResultSchema = z.object({
|
|
|
187
257
|
conversationId: z.string().describe("The conversation holding the findings. Open it to watch."),
|
|
188
258
|
});
|
|
189
259
|
|
|
190
|
-
//
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
export const oldestOpenPush = (pushes: readonly MainlinePush[], project: string): MainlinePush | undefined =>
|
|
194
|
-
pushes.findLast((push) => push.project === project && push.findings.some((finding) => finding.state === "open"));
|
|
260
|
+
// What pushes left in `project`, as the reds name it; undefined while nothing is owed.
|
|
261
|
+
export const pushRedOf = (reds: readonly Red[] | undefined, project: string): Red | undefined =>
|
|
262
|
+
reds?.find((red) => red.source === "push" && red.scope === project);
|
|
195
263
|
|
|
196
|
-
// The conversation id a hand-over of
|
|
197
|
-
// daemon agree on whether an agent is already on
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
};
|
|
264
|
+
// The conversation id a hand-over of a push red wears (attempt 1), derived from when the red began, so the editor and the
|
|
265
|
+
// daemon agree on whether an agent is already on it: pressing again while anything is owed continues the same attempt,
|
|
266
|
+
// and a red that begins after everything was handled starts a fresh one. Undefined when nothing is owed.
|
|
267
|
+
export const pushFixBase = (red: Pick<Red, "scope" | "since"> | undefined): string | undefined =>
|
|
268
|
+
red === undefined ? undefined : pushFixConversationId(red.scope === "" ? "workspace" : red.scope, `red:${red.since}`);
|
|
@@ -1,20 +1,81 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { shrunkSurfaces } from "@intentic/constants/contract-shrink";
|
|
3
4
|
import { packageRoot } from "@intentic/constants/node";
|
|
4
|
-
import {
|
|
5
|
+
import { RAW_ROUTE_LIST } from "../protocol/raw-routes.js";
|
|
6
|
+
import type { RouteMeta } from "../protocol/route-meta.js";
|
|
7
|
+
import { accessChanges, currentLock, serializeLock } from "./contract-lock.js";
|
|
8
|
+
|
|
9
|
+
const committedText = (): string => readFileSync(join(packageRoot(import.meta.url), "contract.lock.json"), "utf8");
|
|
10
|
+
|
|
11
|
+
const ACCESS_MOVED =
|
|
12
|
+
"a route's access moved — run `pnpm --filter @intentic/sandbox-contract lock` and commit contract.lock.json with this change. " +
|
|
13
|
+
"Any change to an existing route's reach (a lower floor, a guest, agent, panel or sync grant, a wider control rung, or a " +
|
|
14
|
+
"tighter one that locks callers out) lands as a `type!:` commit with a `Breaking-Note:` trailer saying who can now reach " +
|
|
15
|
+
"what, or who no longer can.";
|
|
5
16
|
|
|
6
17
|
// contract.lock.json must match the exported schemas; the git-aware half of this check lives in contract-shrink.mjs.
|
|
7
18
|
// Needs 30s: serializing ~500 schemas can run past the unit hang detector under load.
|
|
8
19
|
test(
|
|
9
20
|
"contract.lock.json matches the schemas this package exports",
|
|
10
21
|
() => {
|
|
11
|
-
const committed =
|
|
22
|
+
const committed = committedText();
|
|
23
|
+
const current = currentLock();
|
|
24
|
+
// Named first, so a reach change says which route before the whole-lock diff does.
|
|
25
|
+
expect(accessChanges(committed, serializeLock(current)), ACCESS_MOVED).toEqual([]);
|
|
12
26
|
expect(
|
|
13
|
-
|
|
27
|
+
current,
|
|
14
28
|
"the wire contract moved — run `pnpm --filter @intentic/sandbox-contract lock` and commit contract.lock.json with this change. " +
|
|
15
29
|
"If a schema or field was removed or changed (not added), land it as a `type!:` commit with a `Breaking-Note:` trailer " +
|
|
16
30
|
"saying, in the user's words, what stops working and what to do instead.",
|
|
17
|
-
).toEqual(committed);
|
|
31
|
+
).toEqual(JSON.parse(committed));
|
|
18
32
|
},
|
|
19
33
|
{ timeout: 30_000 },
|
|
20
34
|
);
|
|
35
|
+
|
|
36
|
+
// The wire outside oRPC is pinned the same way: a changed tunnel path, a gone control message or a renamed verdict is a
|
|
37
|
+
// shrink the push gate and the landing draft both flag, where before nothing could see it.
|
|
38
|
+
test("the lock flags a change to the wire no oRPC route carries", () => {
|
|
39
|
+
const base = currentLock() as Record<string, Record<string, unknown>>;
|
|
40
|
+
const moved = structuredClone(base);
|
|
41
|
+
(moved["wire:tunnel"] as { path: string }).path = "/tunnel/v3";
|
|
42
|
+
delete (moved["wire:browser-wire"] as { webTransport?: unknown }).webTransport;
|
|
43
|
+
expect(shrunkSurfaces(base, moved)).toEqual(["wire:browser-wire.webTransport", "wire:tunnel.path"]);
|
|
44
|
+
expect(shrunkSurfaces(moved, base)).toEqual(["wire:tunnel.path"]);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// The lock as it would be with one raw route declared otherwise: the declaration is swapped in place, and back.
|
|
48
|
+
const lockWith = (name: string, change: (declared: RouteMeta) => RouteMeta): Record<string, unknown> => {
|
|
49
|
+
const route = RAW_ROUTE_LIST.find((candidate) => candidate.name === name);
|
|
50
|
+
if (route === undefined) {
|
|
51
|
+
throw new Error(`${name} is gone; pick another raw route with a maintainer floor and no grants`);
|
|
52
|
+
}
|
|
53
|
+
const declared = route.meta;
|
|
54
|
+
Reflect.set(route, "meta", change(declared));
|
|
55
|
+
try {
|
|
56
|
+
return currentLock();
|
|
57
|
+
} finally {
|
|
58
|
+
Reflect.set(route, "meta", declared);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const ROUTE = "POST /system/sync/report";
|
|
63
|
+
|
|
64
|
+
// A lowered floor on any route, not only the ones route-reach.test.ts pins: the lock check names it, and the push gate and
|
|
65
|
+
// the landing draft read it as a shrink that needs a declared break.
|
|
66
|
+
test("lowering one route's floor fails the lock check, naming the route", () => {
|
|
67
|
+
const committed = currentLock();
|
|
68
|
+
const lowered = lockWith(ROUTE, (declared) => ({ ...declared, floor: "viewer" }));
|
|
69
|
+
expect(accessChanges(serializeLock(committed), serializeLock(lowered))).toEqual([`${ROUTE}: floor "maintainer" → "viewer"`]);
|
|
70
|
+
expect(shrunkSurfaces(committed, lowered)).toEqual([`access:${ROUTE}.floor`]);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// A grant a route never declared still has a value in the lock, so granting it is a changed value, not growth that
|
|
74
|
+
// passes; taking one away reads the same way, since it locks out a caller that relied on it.
|
|
75
|
+
test("granting guest or agent reach reads as a shrink, and so does taking it back", () => {
|
|
76
|
+
const committed = currentLock();
|
|
77
|
+
const granted = lockWith(ROUTE, (declared) => ({ ...declared, guest: true, agent: true }));
|
|
78
|
+
expect(accessChanges(serializeLock(committed), serializeLock(granted))).toEqual([`${ROUTE}: agent false → true, guest false → true`]);
|
|
79
|
+
expect(shrunkSurfaces(committed, granted)).toEqual([`access:${ROUTE}.agent`, `access:${ROUTE}.guest`]);
|
|
80
|
+
expect(shrunkSurfaces(granted, committed)).toEqual([`access:${ROUTE}.agent`, `access:${ROUTE}.guest`]);
|
|
81
|
+
});
|
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
// contract.lock.json is every exported schema serialized to canonical JSON Schema, so git can flag a wire-contract
|
|
2
2
|
// change. contract-shrink demands a `!` commit with a Breaking-Note when a schema, field or type shrinks; additions
|
|
3
|
-
// pass freely. Derived from the package's exports, not a hand-kept list
|
|
3
|
+
// pass freely. Derived from the package's exports, not a hand-kept list, plus the wire no oRPC route carries: the
|
|
4
|
+
// manifests the Rust crates that define it write beside their TypeScript (the tunnel, what a browser sees of the front
|
|
5
|
+
// and the edge, and the front's control socket with Node), under `wire:` names no export can take.
|
|
6
|
+
//
|
|
7
|
+
// And who reaches each route: every raw route's and every procedure's RouteMeta, each reach field with its default
|
|
8
|
+
// resolved (route-meta.ts `routeAccess`), under `access:METHOD /path`. Resolved, so every field is a value on every
|
|
9
|
+
// route: a lowered floor, a `guest`, `agent` or `panel` granted, a control rung widened are each a changed value, which
|
|
10
|
+
// contract-shrink reads as a shrink, the same `!` and Breaking-Note a removed field needs. A field left absent could
|
|
11
|
+
// instead be added unseen, since growth passes freely. route-reach.test.ts in the daemon proves the gates honour
|
|
12
|
+
// exactly these declarations; this pins the declarations themselves.
|
|
4
13
|
|
|
14
|
+
import { readFileSync } from "node:fs";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import { packageRoot } from "@intentic/constants/node";
|
|
5
17
|
import { z } from "zod";
|
|
6
18
|
import * as contract from "../index.js";
|
|
7
19
|
|
|
@@ -21,6 +33,57 @@ const sorted = (value: unknown): unknown => {
|
|
|
21
33
|
return value;
|
|
22
34
|
};
|
|
23
35
|
|
|
36
|
+
const ACCESS_PREFIX = "access:";
|
|
37
|
+
|
|
38
|
+
// The access half of the lock, one entry per route with its fields in key order; a procedure also names itself, so a
|
|
39
|
+
// route moving between procedures is seen.
|
|
40
|
+
const accessEntries = (): Record<string, Readonly<Record<string, string | boolean>>> => {
|
|
41
|
+
const entries = new Map<string, Readonly<Record<string, string | boolean>>>();
|
|
42
|
+
for (const route of [...contract.RAW_ROUTE_LIST, ...contract.SANDBOX_ROUTES]) {
|
|
43
|
+
const key = `${ACCESS_PREFIX}${route.method} ${route.path}`;
|
|
44
|
+
if (entries.has(key)) {
|
|
45
|
+
throw new Error(`two routes declare ${route.method} ${route.path}; the lock can record only one reach for it`);
|
|
46
|
+
}
|
|
47
|
+
const access = contract.routeAccess(route.method, route.meta);
|
|
48
|
+
const fields = route.name === `${route.method} ${route.path}` ? access : { ...access, procedure: route.name };
|
|
49
|
+
entries.set(key, Object.fromEntries(Object.entries(fields).toSorted(([a], [b]) => a.localeCompare(b))));
|
|
50
|
+
}
|
|
51
|
+
return Object.fromEntries([...entries].toSorted(([a], [b]) => a.localeCompare(b)));
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// A lock text's access half: route key to its fields. Entries under other names are not its business.
|
|
55
|
+
const accessHalf = (text: string): Record<string, Readonly<Record<string, string | boolean>>> => {
|
|
56
|
+
const lock = z.record(z.string(), z.unknown()).parse(JSON.parse(text));
|
|
57
|
+
return z
|
|
58
|
+
.record(z.string(), z.record(z.string(), z.union([z.string(), z.boolean()])))
|
|
59
|
+
.parse(Object.fromEntries(Object.entries(lock).filter(([key]) => key.startsWith(ACCESS_PREFIX))));
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Each route whose recorded reach differs between two locks, one line naming the route and every field that moved, so a
|
|
63
|
+
// failing lock check says which route's access changed rather than leaving it to a diff of hundreds of entries. Takes
|
|
64
|
+
// the two lock texts, as serializeLock writes them.
|
|
65
|
+
export const accessChanges = (baseText: string, headText: string): string[] => {
|
|
66
|
+
const base = accessHalf(baseText);
|
|
67
|
+
const head = accessHalf(headText);
|
|
68
|
+
const keys = new Set([...Object.keys(base), ...Object.keys(head)]);
|
|
69
|
+
return [...keys].toSorted().flatMap((key) => {
|
|
70
|
+
const route = key.slice(ACCESS_PREFIX.length);
|
|
71
|
+
const was = base[key];
|
|
72
|
+
const now = head[key];
|
|
73
|
+
if (was === undefined || now === undefined) {
|
|
74
|
+
return [`${route}: ${was === undefined ? "new route" : "no longer declared"}`];
|
|
75
|
+
}
|
|
76
|
+
const moved = [...new Set([...Object.keys(was), ...Object.keys(now)])]
|
|
77
|
+
.toSorted()
|
|
78
|
+
.filter((field) => was[field] !== now[field])
|
|
79
|
+
.map((field) => `${field} ${JSON.stringify(was[field]) ?? "absent"} → ${JSON.stringify(now[field]) ?? "absent"}`);
|
|
80
|
+
return moved.length === 0 ? [] : [`${route}: ${moved.join(", ")}`];
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Each written by its crate's own test: `tunnel`, `browser-wire` and `front-wire` under _sandbox/front/crates.
|
|
85
|
+
const WIRE_MANIFESTS = ["browser-wire", "front-wire", "tunnel"] as const;
|
|
86
|
+
|
|
24
87
|
export const currentLock = (): Record<string, unknown> => {
|
|
25
88
|
const lock: Record<string, unknown> = {};
|
|
26
89
|
for (const name of Object.keys(contract).sort()) {
|
|
@@ -38,7 +101,12 @@ export const currentLock = (): Record<string, unknown> => {
|
|
|
38
101
|
lock[name] = "<unserializable>";
|
|
39
102
|
}
|
|
40
103
|
}
|
|
41
|
-
|
|
104
|
+
for (const manifest of WIRE_MANIFESTS) {
|
|
105
|
+
// Read from src/ whether this runs from src/ or dist/: the manifests are data `cargo test` writes, never compiled.
|
|
106
|
+
const text = readFileSync(join(packageRoot(import.meta.url), "src", "front", "generated", `${manifest}.json`), "utf8");
|
|
107
|
+
lock[`wire:${manifest}`] = sorted(JSON.parse(text));
|
|
108
|
+
}
|
|
109
|
+
return { ...lock, ...accessEntries() };
|
|
42
110
|
};
|
|
43
111
|
|
|
44
112
|
// One line per schema, not pretty-printed: keeps the lock short enough that a diff shows which surfaces moved.
|
|
@@ -59,6 +59,14 @@ test.each([
|
|
|
59
59
|
});
|
|
60
60
|
});
|
|
61
61
|
|
|
62
|
+
// The daemon's own reading decides, whatever the watch list says: a wake waiting in its queue keeps it working with no
|
|
63
|
+
// watch at all, and a daemon that says nothing wakes it is not second-guessed from a list it still shows.
|
|
64
|
+
test("an agent the daemon says awaits a wake is still working, and one it says does not is judged on what it holds", () => {
|
|
65
|
+
expect(fixStance(agent({ status: `idle`, awaitingWake: true })).kind).toBe(`working`);
|
|
66
|
+
const watches = [{ id: `watch-a1b2`, note: `CI on the pushed branch`, intervalSeconds: 60, deadlineAt: 9_000 }];
|
|
67
|
+
expect(fixStance(agent({ status: `idle`, awaitingWake: false, watches, diff: { files: 1, insertions: 1, deletions: 0 } })).kind).toBe(`ready`);
|
|
68
|
+
});
|
|
69
|
+
|
|
62
70
|
// Diff present doesn't change a failed status to ready; it only earns a mention in the hint.
|
|
63
71
|
test("an agent that failed after writing files still reads as failed", () => {
|
|
64
72
|
const stance = fixStance(agent({ status: `error`, diff: { files: 2, insertions: 34, deletions: 6 } }));
|
package/src/state/fix-stance.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { turnedAwayCode } from "../policy/turned-away.js";
|
|
2
|
-
import type
|
|
2
|
+
import { type AgentSummary, awaitsWake } from "../schemas/agents.js";
|
|
3
3
|
|
|
4
4
|
/* WHAT BECAME OF THE AGENT A SURFACE SENT AFTER A FAILURE — a red pipeline row, a refused push. */
|
|
5
5
|
|
|
@@ -130,8 +130,8 @@ const RULES: readonly ((agent: AgentSummary) => FixStance | undefined)[] = [
|
|
|
130
130
|
? needsYou("Needs you", "The fix agent has stopped and is waiting on you.")
|
|
131
131
|
: undefined,
|
|
132
132
|
(agent) => (IN_FLIGHT.has(agent.status) ? WORKING : undefined),
|
|
133
|
-
//
|
|
134
|
-
(agent) => ((agent
|
|
133
|
+
// It runs again by itself (a watch, a waiting wake), so neither what it holds nor holding nothing is its last word yet.
|
|
134
|
+
(agent) => (awaitsWake(agent) ? WORKING : undefined),
|
|
135
135
|
(agent) => (holdingWork(agent) ? READY : undefined),
|
|
136
136
|
(agent) => (agent.status === "landed" ? LANDED : undefined),
|
|
137
137
|
];
|
|
@@ -74,6 +74,10 @@ export const HISTORY_STATE_FILES: readonly StateFile[] = [
|
|
|
74
74
|
{ path: "backups/", portability: "derived" },
|
|
75
75
|
// Bundles being taken in, mid-review; excluded as someone else's. Deleted on apply/abandon, swept after a crash.
|
|
76
76
|
{ path: "arrivals/", portability: "derived" },
|
|
77
|
+
// The agent engines this sandbox installed beside the image's own, each with its pointer file; reinstalled on demand.
|
|
78
|
+
{ path: "engines/", portability: "derived" },
|
|
79
|
+
// An update the host downloaded and built for this sandbox (`ic sandbox prepare`); about this machine only.
|
|
80
|
+
{ path: "update-staged.json", portability: "derived" },
|
|
77
81
|
|
|
78
82
|
/* ---- credentials ---- */
|
|
79
83
|
|
|
@@ -132,7 +136,7 @@ export const HISTORY_STATE_FILES: readonly StateFile[] = [
|
|
|
132
136
|
{ path: "converting/", portability: "secret", note: "An update's undo record stays with the sandbox it happened on." },
|
|
133
137
|
// Which conversion episodes are open or recently committed, and earlier addresses awaiting removal.
|
|
134
138
|
{ path: "state-journal.json", portability: "derived" },
|
|
135
|
-
// Every conversation the import of the pre-1.308 layout settled (the daemon's
|
|
139
|
+
// Every conversation the import of the pre-1.308 layout settled (the daemon's conversations/registry/pre-1308-import.ts),
|
|
136
140
|
// so one deleted after its import is not brought back from the old files still on disk. About this volume only.
|
|
137
141
|
{ path: "pre-1308-import.json", portability: "derived" },
|
|
138
142
|
];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { declaredTestNote, landedCommitMessage } from "./landed-commit.js";
|
|
1
|
+
import { declaredAllows, declaredTestNote, landedCommitMessage } from "./landed-commit.js";
|
|
2
2
|
|
|
3
3
|
// The push gate reads a weakening's declaration off the commit (assertion-ratchet.mjs), so the trailer must survive
|
|
4
4
|
// the trip from the conversation's last word into the message a land commits under.
|
|
@@ -15,3 +15,14 @@ test("a Test-Note is read only from a line that starts with one and says somethi
|
|
|
15
15
|
expect(declaredTestNote("I kept a Test-Note: nowhere near the start")).toBeUndefined();
|
|
16
16
|
expect(declaredTestNote("Test-Note: \n")).toBeUndefined();
|
|
17
17
|
});
|
|
18
|
+
|
|
19
|
+
// An exception a conversation declared rides its land's commit, one trailer per line, for the checks that read them.
|
|
20
|
+
test("an Allow line is read from each line that starts with one, and each commits as its own trailer", () => {
|
|
21
|
+
const said = "Done.\n\nAllow: layout — the fixtures folder is one suite's data\nAllow: paths — the test spells a root on purpose\nI would Allow: nothing here";
|
|
22
|
+
const allows = declaredAllows(said);
|
|
23
|
+
expect(allows).toEqual(["layout — the fixtures folder is one suite's data", "paths — the test spells a root on purpose"]);
|
|
24
|
+
expect(declaredAllows("Allow: \n")).toEqual([]);
|
|
25
|
+
expect(landedCommitMessage({ subject: "fix: tabs", testNote: "Rows became a table.", allows })).toBe(
|
|
26
|
+
"fix: tabs\n\nTest-Note: Rows became a table.\nAllow: layout — the fixtures folder is one suite's data\nAllow: paths — the test spells a root on purpose",
|
|
27
|
+
);
|
|
28
|
+
});
|
|
@@ -7,6 +7,7 @@ export const landedCommitMessage = (landed: LandedMessage): string => {
|
|
|
7
7
|
landed.note === undefined ? "" : `Release-Note: ${landed.note}`,
|
|
8
8
|
landed.breaking === undefined ? "" : `Breaking-Note: ${landed.breaking}`,
|
|
9
9
|
landed.testNote === undefined ? "" : `Test-Note: ${landed.testNote}`,
|
|
10
|
+
...(landed.allows ?? []).map((allow) => `Allow: ${allow}`),
|
|
10
11
|
]
|
|
11
12
|
.filter((line) => line !== "")
|
|
12
13
|
.join("\n");
|
|
@@ -15,3 +16,7 @@ export const landedCommitMessage = (landed: LandedMessage): string => {
|
|
|
15
16
|
|
|
16
17
|
// The `Test-Note:` line a conversation ended its last message with, when it declared a weakened test on purpose.
|
|
17
18
|
export const declaredTestNote = (said: string): string | undefined => /^Test-Note:[ \t]*(\S.*)$/m.exec(said)?.[1]?.trim();
|
|
19
|
+
|
|
20
|
+
// The `Allow: <check> — <reason>` lines a conversation ended its last message with, each an exception it declared for
|
|
21
|
+
// its change on purpose; the check after the land and the push read them off the landed commit.
|
|
22
|
+
export const declaredAllows = (said: string): string[] => [...said.matchAll(/^Allow:[ \t]*(\S.*)$/gm)].map((match) => match[1]!.trim());
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { headStreak, nextStreak } from "./red-streak.js";
|
|
2
|
+
|
|
3
|
+
// The five places that asked "is this still the same red" asked it five ways; these are the two answers they share.
|
|
4
|
+
|
|
5
|
+
describe(`a streak followed one observation at a time`, () => {
|
|
6
|
+
test(`begins at the first red, keeps that start through every red after it, and ends at anything else`, () => {
|
|
7
|
+
const first = nextStreak(undefined, true, 10);
|
|
8
|
+
expect(first).toEqual({ since: 10, count: 1 });
|
|
9
|
+
const second = nextStreak(first, true, 20);
|
|
10
|
+
expect(second).toEqual({ since: 10, count: 2 });
|
|
11
|
+
expect(nextStreak(second, false, 30)).toBeUndefined();
|
|
12
|
+
expect(nextStreak(undefined, false, 30)).toBeUndefined();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test(`begins again when the red is a different failure`, () => {
|
|
16
|
+
expect(nextStreak({ since: 10, count: 3 }, true, 40, false)).toEqual({ since: 40, count: 1 });
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe(`a streak read off a history`, () => {
|
|
21
|
+
test(`is the unbroken run of red at the head, newest first`, () => {
|
|
22
|
+
const history = [`red-3`, `red-2`, `green-1`, `red-0`];
|
|
23
|
+
expect(headStreak(history, (item) => item.startsWith(`red`))).toEqual([`red-3`, `red-2`]);
|
|
24
|
+
expect(headStreak([`green`, `red`], (item) => item === `red`)).toEqual([]);
|
|
25
|
+
expect(headStreak([`red`, `red`], (item) => item === `red`)).toEqual([`red`, `red`]);
|
|
26
|
+
expect(headStreak([], () => true)).toEqual([]);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// ONE DEFINITION OF A RED STREAK, for every source that goes red: a land check's project (verify-store.ts), main's CI
|
|
2
|
+
// (repair-gate.ts), a pipeline branch on the board (the pipelines extension's ciStreaks.ts) and the Main line's older
|
|
3
|
+
// daemons (the editor's mainlineView.ts). A streak is the unbroken run of red at the head: a red extends it, anything
|
|
4
|
+
// else ends it, and `since` is where it began, which is what names one failure across every red that continues it.
|
|
5
|
+
|
|
6
|
+
export interface Streak {
|
|
7
|
+
// When (or at which run) the streak began: the first red of the unbroken run.
|
|
8
|
+
readonly since: number;
|
|
9
|
+
// How many reds in a row it holds.
|
|
10
|
+
readonly count: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* One observation's effect on the streak before it. A red continues it, unless `continues` says this red is a different
|
|
15
|
+
* failure (CI: no failed job repeats), which begins one of its own; anything else ends it.
|
|
16
|
+
*/
|
|
17
|
+
export const nextStreak = (previous: Streak | undefined, red: boolean, at: number, continues = true): Streak | undefined => {
|
|
18
|
+
if (!red) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
return previous !== undefined && continues ? { since: previous.since, count: previous.count + 1 } : { since: at, count: 1 };
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/** The streak a newest-first history stands on: its unbroken run of red at the head, newest first; empty when the head is not red. */
|
|
25
|
+
export const headStreak = <T>(newestFirst: readonly T[], isRed: (item: T) => boolean): T[] => {
|
|
26
|
+
const end = newestFirst.findIndex((item) => !isRed(item));
|
|
27
|
+
return end === -1 ? [...newestFirst] : newestFirst.slice(0, end);
|
|
28
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { STATE_DIR } from "@intentic/constants";
|
|
1
|
+
import { HISTORY_ROOT, STATE_DIR } from "@intentic/constants";
|
|
2
2
|
import type { FileContribution } from "@intentic/extension-manifest";
|
|
3
3
|
import {
|
|
4
4
|
BACKED_UP_STATE_PATHS,
|
|
@@ -129,6 +129,12 @@ describe(`isReportedManifest`, () => {
|
|
|
129
129
|
// What `relative` gives the daemon for a manifest kept under /history.
|
|
130
130
|
expect(isReportedManifest(`../../history/settings.json`)).toBe(false);
|
|
131
131
|
});
|
|
132
|
+
|
|
133
|
+
it(`reports the conversation registry on the daemon's volume by its absolute path, and nothing else there`, () => {
|
|
134
|
+
expect(isReportedManifest(`${HISTORY_ROOT}/conversations.db`)).toBe(true);
|
|
135
|
+
expect(isReportedManifest(`${HISTORY_ROOT}/state-journal.json`)).toBe(false);
|
|
136
|
+
expect(isReportedManifest(`../history/conversations.db`)).toBe(false);
|
|
137
|
+
});
|
|
132
138
|
});
|
|
133
139
|
|
|
134
140
|
describe(`WORKSPACE_STATE_FILES`, () => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { STATE_DIR } from "@intentic/constants";
|
|
1
|
+
import { HISTORY_ROOT, STATE_DIR } from "@intentic/constants";
|
|
2
2
|
import type { FileContribution } from "@intentic/extension-manifest";
|
|
3
3
|
import type { StateFile } from "./state-portability.js";
|
|
4
4
|
|
|
@@ -601,9 +601,17 @@ export const REPORTED_MANIFEST_PATHS: readonly string[] = WORKSPACE_STATE_FILES.
|
|
|
601
601
|
(file) => file.path,
|
|
602
602
|
);
|
|
603
603
|
|
|
604
|
+
// Files on the daemon's own volume whose unreadable records are reported too, named by their absolute path there: the
|
|
605
|
+
// conversation registry's rows, one of which this build could not read costs that conversation until a build that can
|
|
606
|
+
// runs. Reported, never repaired from the card (no route edits them), and refreshed whenever the card is fetched.
|
|
607
|
+
export const REPORTED_VOLUME_FILES: readonly string[] = [`${HISTORY_ROOT}/conversations.db`];
|
|
608
|
+
|
|
604
609
|
// Accepts either separator; the daemon holds these as platform paths and normalizing at every call site is what
|
|
605
610
|
// eventually gets forgotten.
|
|
606
|
-
export const isReportedManifest = (relPath: string): boolean =>
|
|
611
|
+
export const isReportedManifest = (relPath: string): boolean => {
|
|
612
|
+
const path = relPath.replaceAll("\\", "/");
|
|
613
|
+
return REPORTED_MANIFEST_PATHS.includes(path) || REPORTED_VOLUME_FILES.includes(path);
|
|
614
|
+
};
|
|
607
615
|
|
|
608
616
|
// Entries directly under `.intentic/` the file API refuses to read, write, move or delete for anyone, owner
|
|
609
617
|
// included (workspace/workspace-files.ts enforces it). Declared here so the browser can draw the same rule instead
|