@intentius/chant 0.49.0 → 0.51.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/dist/audit/catalog.d.ts +13 -3
- package/dist/audit/catalog.d.ts.map +1 -1
- package/dist/audit/core.d.ts +9 -0
- package/dist/audit/core.d.ts.map +1 -1
- package/dist/audit/discover.d.ts +6 -0
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/audit/fetch.d.ts.map +1 -1
- package/dist/audit/report-html.d.ts.map +1 -1
- package/dist/audit/report-model.d.ts +6 -0
- package/dist/audit/report-model.d.ts.map +1 -1
- package/dist/audit/report.d.ts.map +1 -1
- package/dist/audit/rules-doc.d.ts.map +1 -1
- package/dist/audit/secrets.d.ts +95 -0
- package/dist/audit/secrets.d.ts.map +1 -0
- package/dist/audit/wrangler.d.ts +33 -0
- package/dist/audit/wrangler.d.ts.map +1 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/cli/commands/audit.d.ts +7 -0
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/build.d.ts +23 -0
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/components.d.ts +31 -0
- package/dist/cli/handlers/components.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.d.ts +11 -0
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/op-progress.d.ts +57 -0
- package/dist/cli/handlers/op-progress.d.ts.map +1 -0
- package/dist/cli/handlers/operator.d.ts +32 -0
- package/dist/cli/handlers/operator.d.ts.map +1 -0
- package/dist/cli/handlers/run-client.d.ts +21 -1
- package/dist/cli/handlers/run-client.d.ts.map +1 -1
- package/dist/cli/handlers/run-report.d.ts.map +1 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/handlers/scenario.d.ts +39 -0
- package/dist/cli/handlers/scenario.d.ts.map +1 -0
- package/dist/cli/handlers/search.d.ts +22 -0
- package/dist/cli/handlers/search.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/op-tools.d.ts.map +1 -1
- package/dist/cli/mcp/resource-handlers.d.ts.map +1 -1
- package/dist/cli/mcp/server.d.ts +35 -2
- package/dist/cli/mcp/server.d.ts.map +1 -1
- package/dist/cli/mcp/types.d.ts +29 -1
- package/dist/cli/mcp/types.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +47 -3
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/docs-rule-scanning.d.ts.map +1 -1
- package/dist/components/capability.d.ts +17 -2
- package/dist/components/capability.d.ts.map +1 -1
- package/dist/components/cli-support.d.ts +7 -0
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/component.d.ts +15 -0
- package/dist/components/component.d.ts.map +1 -1
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/run-progress.d.ts +7 -5
- package/dist/components/run-progress.d.ts.map +1 -1
- package/dist/components/verbs/index.d.ts +6 -1
- package/dist/components/verbs/index.d.ts.map +1 -1
- package/dist/components/verbs/run-agent.d.ts +499 -0
- package/dist/components/verbs/run-agent.d.ts.map +1 -0
- package/dist/components/verbs/sign.d.ts +30 -0
- package/dist/components/verbs/sign.d.ts.map +1 -1
- package/dist/composite.d.ts +6 -1
- package/dist/composite.d.ts.map +1 -1
- package/dist/discovery/collect.d.ts.map +1 -1
- package/dist/discovery/fold-import.d.ts +15 -1
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/fold-rank.d.ts +66 -0
- package/dist/discovery/fold-rank.d.ts.map +1 -0
- package/dist/discovery/index.d.ts +15 -0
- package/dist/discovery/index.d.ts.map +1 -1
- package/dist/discovery/param-deps.d.ts +17 -0
- package/dist/discovery/param-deps.d.ts.map +1 -0
- package/dist/fold/fold.d.ts +55 -2
- package/dist/fold/fold.d.ts.map +1 -1
- package/dist/fold/subset.d.ts +21 -14
- package/dist/fold/subset.d.ts.map +1 -1
- package/dist/lexicon-schema.d.ts +2 -0
- package/dist/lexicon-schema.d.ts.map +1 -1
- package/dist/lexicon.d.ts +134 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/assert-live.d.ts +77 -0
- package/dist/lifecycle/assert-live.d.ts.map +1 -0
- package/dist/lifecycle/change-set.d.ts +17 -0
- package/dist/lifecycle/change-set.d.ts.map +1 -1
- package/dist/lifecycle/converge-ledger.d.ts +90 -0
- package/dist/lifecycle/converge-ledger.d.ts.map +1 -0
- package/dist/lifecycle/deep-diff.d.ts +18 -0
- package/dist/lifecycle/deep-diff.d.ts.map +1 -1
- package/dist/lifecycle/deep-observe.d.ts +9 -1
- package/dist/lifecycle/deep-observe.d.ts.map +1 -1
- package/dist/lifecycle/disruption.d.ts +96 -0
- package/dist/lifecycle/disruption.d.ts.map +1 -0
- package/dist/lifecycle/gate-ledger.d.ts +33 -0
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -0
- package/dist/lifecycle/git.d.ts +145 -21
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/index.d.ts +6 -0
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/lease.d.ts +113 -0
- package/dist/lifecycle/lease.d.ts.map +1 -0
- package/dist/lifecycle/replay.d.ts +2 -0
- package/dist/lifecycle/replay.d.ts.map +1 -1
- package/dist/lifecycle/scenario-eval.d.ts +42 -0
- package/dist/lifecycle/scenario-eval.d.ts.map +1 -0
- package/dist/lifecycle/scenario.d.ts +163 -0
- package/dist/lifecycle/scenario.d.ts.map +1 -0
- package/dist/lifecycle/symptoms.d.ts +63 -0
- package/dist/lifecycle/symptoms.d.ts.map +1 -0
- package/dist/lint/output-docs.d.ts +94 -0
- package/dist/lint/output-docs.d.ts.map +1 -0
- package/dist/lint/post-synth.d.ts +29 -0
- package/dist/lint/post-synth.d.ts.map +1 -1
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.d.ts +11 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.d.ts.map +1 -0
- package/dist/lsp/lexicon-providers.d.ts +7 -0
- package/dist/lsp/lexicon-providers.d.ts.map +1 -1
- package/dist/op/activity-contract.d.ts +139 -0
- package/dist/op/activity-contract.d.ts.map +1 -0
- package/dist/op/builders.d.ts +42 -2
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/converge-rule.d.ts +161 -0
- package/dist/op/converge-rule.d.ts.map +1 -0
- package/dist/op/generate-pipeline.d.ts +39 -0
- package/dist/op/generate-pipeline.d.ts.map +1 -0
- package/dist/op/index.d.ts +14 -0
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/local-executor.d.ts +7 -1
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/op-verb-class.d.ts +42 -0
- package/dist/op/op-verb-class.d.ts.map +1 -0
- package/dist/op/operator.d.ts +128 -0
- package/dist/op/operator.d.ts.map +1 -0
- package/dist/op/step-output-ref.d.ts +187 -0
- package/dist/op/step-output-ref.d.ts.map +1 -0
- package/dist/op/types.d.ts +18 -1
- package/dist/op/types.d.ts.map +1 -1
- package/dist/provenance.d.ts +73 -3
- package/dist/provenance.d.ts.map +1 -1
- package/dist/runtime-adapter.d.ts +7 -1
- package/dist/runtime-adapter.d.ts.map +1 -1
- package/dist/serializer.d.ts +18 -0
- package/dist/serializer.d.ts.map +1 -1
- package/dist/testing.d.ts +23 -2
- package/dist/testing.d.ts.map +1 -1
- package/dist/toml.d.ts +40 -5
- package/dist/toml.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/audit/catalog.test.ts +1 -1
- package/src/audit/catalog.ts +75 -3
- package/src/audit/core.ts +9 -0
- package/src/audit/discover.ts +29 -2
- package/src/audit/fetch.test.ts +216 -3
- package/src/audit/fetch.ts +270 -59
- package/src/audit/report-html.ts +5 -2
- package/src/audit/report-model.ts +9 -0
- package/src/audit/report.test.ts +22 -0
- package/src/audit/report.ts +3 -2
- package/src/audit/rules-doc.ts +2 -0
- package/src/audit/secrets.test.ts +303 -0
- package/src/audit/secrets.ts +406 -0
- package/src/audit/wrangler.test.ts +230 -0
- package/src/audit/wrangler.ts +290 -0
- package/src/build.ts +8 -3
- package/src/cli/command-group.ts +1 -1
- package/src/cli/commands/__fixtures__/schemas/sarif-2.1.0.schema.json +2882 -0
- package/src/cli/commands/audit.test.ts +215 -1
- package/src/cli/commands/audit.ts +86 -17
- package/src/cli/commands/build.test.ts +167 -2
- package/src/cli/commands/build.ts +114 -23
- package/src/cli/handlers/build.ts +2 -0
- package/src/cli/handlers/components.test.ts +199 -1
- package/src/cli/handlers/components.ts +160 -3
- package/src/cli/handlers/graph.test.ts +20 -0
- package/src/cli/handlers/graph.ts +10 -1
- package/src/cli/handlers/lifecycle.test.ts +90 -0
- package/src/cli/handlers/lifecycle.ts +30 -5
- package/src/cli/handlers/op-progress.test.ts +202 -0
- package/src/cli/handlers/op-progress.ts +192 -0
- package/src/cli/handlers/operator.test.ts +255 -0
- package/src/cli/handlers/operator.ts +240 -0
- package/src/cli/handlers/run-client.test.ts +82 -0
- package/src/cli/handlers/run-client.ts +85 -2
- package/src/cli/handlers/run-report.test.ts +62 -0
- package/src/cli/handlers/run-report.ts +20 -58
- package/src/cli/handlers/run.test.ts +144 -0
- package/src/cli/handlers/run.ts +40 -18
- package/src/cli/handlers/scenario.test.ts +456 -0
- package/src/cli/handlers/scenario.ts +330 -0
- package/src/cli/handlers/search-drift.test.ts +263 -0
- package/src/cli/handlers/search.ts +150 -1
- package/src/cli/main.test.ts +23 -0
- package/src/cli/main.ts +81 -1
- package/src/cli/mcp/op-tools.ts +17 -6
- package/src/cli/mcp/resource-handlers.ts +13 -5
- package/src/cli/mcp/server.test.ts +265 -2
- package/src/cli/mcp/server.ts +84 -7
- package/src/cli/mcp/types.ts +27 -1
- package/src/cli/registry.ts +47 -3
- package/src/codegen/docs-rule-scanning.test.ts +42 -0
- package/src/codegen/docs-rule-scanning.ts +25 -2
- package/src/components/README.md +7 -0
- package/src/components/capability.ts +17 -2
- package/src/components/cli-support.test.ts +17 -0
- package/src/components/cli-support.ts +13 -1
- package/src/components/component-schema.test.ts +32 -0
- package/src/components/component.schema.json +6 -0
- package/src/components/component.test.ts +21 -0
- package/src/components/component.ts +15 -0
- package/src/components/driver.ts +12 -4
- package/src/components/run-progress.ts +9 -5
- package/src/components/verbs/index.ts +6 -1
- package/src/components/verbs/run-agent.test.ts +683 -0
- package/src/components/verbs/run-agent.ts +786 -0
- package/src/components/verbs/sign.test.ts +19 -0
- package/src/components/verbs/sign.ts +34 -2
- package/src/composite.ts +31 -2
- package/src/discovery/collect.ts +11 -2
- package/src/discovery/fold-import.test.ts +54 -0
- package/src/discovery/fold-import.ts +178 -38
- package/src/discovery/fold-rank.test.ts +197 -0
- package/src/discovery/fold-rank.ts +346 -0
- package/src/discovery/index.ts +16 -1
- package/src/discovery/param-deps.test.ts +118 -0
- package/src/discovery/param-deps.ts +170 -0
- package/src/fold/fold.test.ts +6 -2
- package/src/fold/fold.ts +184 -3
- package/src/fold/subset.test.ts +82 -19
- package/src/fold/subset.ts +79 -41
- package/src/lexicon-schema.ts +3 -0
- package/src/lexicon.ts +154 -2
- package/src/lifecycle/assert-live.test.ts +125 -0
- package/src/lifecycle/assert-live.ts +154 -0
- package/src/lifecycle/change-set.ts +35 -3
- package/src/lifecycle/converge-ledger.test.ts +199 -0
- package/src/lifecycle/converge-ledger.ts +179 -0
- package/src/lifecycle/deep-diff.test.ts +79 -1
- package/src/lifecycle/deep-diff.ts +23 -0
- package/src/lifecycle/deep-observe.ts +13 -2
- package/src/lifecycle/disruption.test.ts +186 -0
- package/src/lifecycle/disruption.ts +224 -0
- package/src/lifecycle/gate-ledger.test.ts +103 -0
- package/src/lifecycle/gate-ledger.ts +140 -0
- package/src/lifecycle/git.test.ts +430 -0
- package/src/lifecycle/git.ts +446 -84
- package/src/lifecycle/index.ts +6 -0
- package/src/lifecycle/lease.test.ts +343 -0
- package/src/lifecycle/lease.ts +270 -0
- package/src/lifecycle/replay.test.ts +25 -0
- package/src/lifecycle/replay.ts +11 -3
- package/src/lifecycle/scenario-eval.test.ts +199 -0
- package/src/lifecycle/scenario-eval.ts +158 -0
- package/src/lifecycle/scenario.test.ts +195 -0
- package/src/lifecycle/scenario.ts +321 -0
- package/src/lifecycle/symptoms.test.ts +116 -0
- package/src/lifecycle/symptoms.ts +126 -0
- package/src/lint/output-docs.test.ts +220 -0
- package/src/lint/output-docs.ts +204 -0
- package/src/lint/post-synth.test.ts +97 -0
- package/src/lint/post-synth.ts +45 -0
- package/src/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.ts +26 -0
- package/src/lint/rules/comp/comp.test.ts +49 -1
- package/src/lint/rules/evl001-non-literal-expression.test.ts +8 -3
- package/src/lint/rules/evl001-non-literal-expression.ts +6 -6
- package/src/lsp/lexicon-providers.test.ts +44 -0
- package/src/lsp/lexicon-providers.ts +11 -1
- package/src/op/activity-contract.test.ts +180 -0
- package/src/op/activity-contract.ts +278 -0
- package/src/op/builders-exports.test.ts +17 -1
- package/src/op/builders.ts +59 -5
- package/src/op/converge-rule.test.ts +179 -0
- package/src/op/converge-rule.ts +311 -0
- package/src/op/generate-pipeline.test.ts +53 -0
- package/src/op/generate-pipeline.ts +99 -0
- package/src/op/index.ts +30 -0
- package/src/op/local-executor.test.ts +92 -0
- package/src/op/local-executor.ts +52 -10
- package/src/op/local-output.ts +1 -1
- package/src/op/op-verb-class.test.ts +126 -0
- package/src/op/op-verb-class.ts +115 -0
- package/src/op/operator.test.ts +346 -0
- package/src/op/operator.ts +213 -0
- package/src/op/step-output-ref.test.ts +334 -0
- package/src/op/step-output-ref.ts +453 -0
- package/src/op/types.ts +18 -1
- package/src/provenance.test.ts +151 -4
- package/src/provenance.ts +118 -4
- package/src/runtime-adapter.ts +31 -10
- package/src/serializer.ts +18 -0
- package/src/testing.test.ts +89 -2
- package/src/testing.ts +63 -3
- package/src/toml.test.ts +157 -384
- package/src/toml.ts +371 -5
|
@@ -0,0 +1,786 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `run-agent` — phase 1 design: capability schema + registry entry (#1941,
|
|
3
|
+
* epic #1564 "run-agent as a bounded, attested component capability").
|
|
4
|
+
*
|
|
5
|
+
* **Decided (2026-08-25, maintainer comment on #1564).** The agent turn
|
|
6
|
+
* executes on a fly [Sprite](https://sprites.dev), via chant's own sprite
|
|
7
|
+
* lifecycle activities (`lexicons/fly/src/op/activities/sprites.ts` —
|
|
8
|
+
* `spriteCreate`/`spriteCheckpoint`/`spriteExec`/`spriteRestore`/`spriteDestroy`,
|
|
9
|
+
* plus the filesystem activities in `sprite-fs.ts` —
|
|
10
|
+
* `spriteWriteFile`/`spriteReadFile`), **not** fountain's hosted `fountainRun`
|
|
11
|
+
* path (`lexicons/fountain/src/op/activities/fountain-run.ts`): `fountainRun`
|
|
12
|
+
* polls a fountain-managed `Conversation` to a terminal HTTP status and gives
|
|
13
|
+
* chant no checkpoint control, but the epic's compensation story needs chant
|
|
14
|
+
* to own the sprite directly. Compensation is checkpoint-before/restore-on-unwind
|
|
15
|
+
* — "the environment is the transaction," no hand-written inverse action — the
|
|
16
|
+
* same pattern `examples/sprites-agent-task/ops/guarded-task.op.ts` already
|
|
17
|
+
* demonstrates at the Op layer. The offline path is `sprites-fake`/
|
|
18
|
+
* `sprites-emulator` (spritzer), the same `SPRITES_BASE_URL` endpoint-override
|
|
19
|
+
* story `sprites.ts` documents.
|
|
20
|
+
*
|
|
21
|
+
* **Input tracks fountain's `Agent` surface as it stands today**
|
|
22
|
+
* (`lexicons/fountain/src/spec/fountain-openapi.snapshot.json`): `agent` is
|
|
23
|
+
* resolved the same way `fountainRun`'s `resolveAgentId` resolves a name/id
|
|
24
|
+
* against `/api/agents`; `task` is `PromptRequest`-shaped (`prompt` +
|
|
25
|
+
* optional `images`, `ImageInput`'s exact shape). The output's `turn` facts
|
|
26
|
+
* track `Turn`'s shape (`status`/`exit_code`/`started_at`/`ended_at`), even
|
|
27
|
+
* though the turn runs on a chant-owned sprite rather than a fountain-managed
|
|
28
|
+
* `Sandbox` — fountain supplies the *declarative* facts (which model, which
|
|
29
|
+
* runtime, which egress policy via `Environment`), chant drives the
|
|
30
|
+
* *imperative* execution. Revisit only if `BinaryBourbon/fountain#586` (the
|
|
31
|
+
* `Estate` proposal) lands a breaking shape.
|
|
32
|
+
*
|
|
33
|
+
* **Placement.** Registered the same way `docker-build` is (./build.ts) — a
|
|
34
|
+
* factory over an injectable executor seam (`SpriteActivities` below, the
|
|
35
|
+
* `run-agent` analogue of ./cloud-executor.ts's `CloudExecutor`) — so core
|
|
36
|
+
* carries the typed schema without a hard package dependency on
|
|
37
|
+
* `@intentius/chant-lexicon-fly` (core stays cloud/vendor-agnostic in its
|
|
38
|
+
* dependency graph, see docs/components/cloud-boundary; the same reasoning
|
|
39
|
+
* that keeps the AWS leaves in `@intentius/chant-lexicon-aws` rather than
|
|
40
|
+
* here). Unlike the AWS leaves, there is today no fly-lexicon capability
|
|
41
|
+
* plugin for this verb to live in instead, and `run-agent` is a foundational,
|
|
42
|
+
* pipeline-level primitive in the same vein as `docker-build` or
|
|
43
|
+
* `wrangler-deploy` (./wrangler.ts) rather than a swappable per-cloud leaf —
|
|
44
|
+
* the same tradeoff `wrangler.ts`'s module doc weighs for the Cloudflare
|
|
45
|
+
* Workers verbs. Revisit if a broader agent-execution lexicon ever owns this
|
|
46
|
+
* seam instead.
|
|
47
|
+
*
|
|
48
|
+
* **Scope across phases.** Phase 1 (#1941) shipped the typed contract only —
|
|
49
|
+
* `run`/`rollback` were stubs throwing `CapabilityNotImplementedError`, per
|
|
50
|
+
* ../capability.ts's own module doc ("Verb implementations live under
|
|
51
|
+
* `./verbs/*` as typed stubs — no cloud calls, no side effects. Cloud
|
|
52
|
+
* implementations are a later phase"). `rollbackPolicy: "native"` was declared
|
|
53
|
+
* from phase 1 (so COMP003, ../lint/rules/comp/comp003-mutating-no-rollback.ts,
|
|
54
|
+
* never requires a `noRollback` opt-out for this verb once it composes into a
|
|
55
|
+
* component) even before the paired `rollback` body was real — the *policy*
|
|
56
|
+
* was the phase 1 design commitment, the *implementation* is phase 2 (#1942,
|
|
57
|
+
* below). This capability is deliberately not yet registered in any
|
|
58
|
+
* `CapabilityPlugin`'s `STARTER_VERB_FAMILIES`-equivalent set — the fly
|
|
59
|
+
* lexicon's own plugin (`lexicons/fly/src/components/capability-plugin.ts`)
|
|
60
|
+
* registers it under its own `FLY_VERB_FAMILIES`, but that is a separate,
|
|
61
|
+
* later decision from "does core's sequencing logic actually work," which
|
|
62
|
+
* this module answers. #1943 ("Provenance + attestation") owns the
|
|
63
|
+
* `provenance.sourceRef` transcript-hash basis and verify-gate interop; #1944
|
|
64
|
+
* owns the conformance/contract test suite (saga-unwind restore, COMP003
|
|
65
|
+
* refusal) beyond this module's own unit tests.
|
|
66
|
+
*
|
|
67
|
+
* **#1942 (phase 2, this module's `run`/`rollback` body) resolved the
|
|
68
|
+
* following:**
|
|
69
|
+
* - **The exec-throw finding** (pre-merge review of #1946, recorded on
|
|
70
|
+
* #1942): the real `spriteExec` (`lexicons/fly/src/op/activities/sprites.ts`)
|
|
71
|
+
* throws on any non-zero exit, so a *thin* `SpriteActivities.exec` adapter
|
|
72
|
+
* can only ever produce `turn.status: "failed"` without throwing if it
|
|
73
|
+
* reclassifies that throw itself. That reclassification is the fly
|
|
74
|
+
* lexicon adapter's job (`lexicons/fly/src/components/run-agent.ts`,
|
|
75
|
+
* option (a) from the review comment) — `SpriteActivities.exec`'s
|
|
76
|
+
* contract, as this module consumes it, is: **resolve** with `exitCode`
|
|
77
|
+
* for any ordinary command outcome (zero or non-zero), **reject** only for
|
|
78
|
+
* a genuine transport/infra failure. `run()` below trusts that contract
|
|
79
|
+
* completely: it never wraps `sprites.exec` in a try/catch, so a rejection
|
|
80
|
+
* from the injected `sprites` always propagates as a genuine `run()`
|
|
81
|
+
* failure (triggering saga rollback in ../driver.ts), while an ordinary
|
|
82
|
+
* non-zero exit becomes `turn.status: "failed"` on a normal return —
|
|
83
|
+
* exactly the non-throwing first-class result the type already declared.
|
|
84
|
+
* - **Rollback identity (#1944 closes the durable-path gap left open here).**
|
|
85
|
+
* `rollback(ctx, input, output)` receives the identical `input` object
|
|
86
|
+
* `run(ctx, input)` was called with, plus (since #1944) the exact `output`
|
|
87
|
+
* `run()` returned — `../capability.ts`'s `Capability.rollback` grew an
|
|
88
|
+
* optional third parameter for precisely this, and ../driver.ts's saga
|
|
89
|
+
* unwind (both the local in-process path and, via
|
|
90
|
+
* `lexicons/temporal/src/component-op/{activities,serializer}.ts`, the
|
|
91
|
+
* durable Temporal path) always threads it through. `run()` records the
|
|
92
|
+
* sprite id and exact pre-run checkpoint id two ways: in a private
|
|
93
|
+
* `WeakMap` keyed by the exact `input` object (works only when `rollback`
|
|
94
|
+
* is called with that same object — true in-process, e.g. this module's
|
|
95
|
+
* own tests calling `rollback()` directly after `run()`), and — the
|
|
96
|
+
* durable-safe channel — as `output.spriteId`/`output.checkpointId`,
|
|
97
|
+
* already part of `RunAgentOutput` (#1943). `rollback()` prefers `output`'s
|
|
98
|
+
* fields when present, falling back to the `WeakMap` only when a caller
|
|
99
|
+
* never threads `output` through (backward-compatible for any capability
|
|
100
|
+
* caller not yet passing it). **The checkpoint id, not the comment, is
|
|
101
|
+
* what `rollback()` restores by** when either source has one:
|
|
102
|
+
* `sprites.restore({ id, checkpoint: checkpointId })`, which
|
|
103
|
+
* `spriteRestore`'s explicit-id resolution wins over comment on
|
|
104
|
+
* (`lexicons/fly/src/op/activities/sprites.ts`). Restoring by comment alone
|
|
105
|
+
* is unsafe on a reused sprite — two `run()` calls sharing the default
|
|
106
|
+
* `"pre-run"` comment would make a comment-based restore always resolve to
|
|
107
|
+
* the *newest* matching checkpoint, so rolling back the first run would
|
|
108
|
+
* restore the second run's checkpoint (which already contains the first
|
|
109
|
+
* run's mutation) instead of undoing it. Comment-based restore
|
|
110
|
+
* (`workspace.checkpointComment`, default `"pre-run"`) is therefore only a
|
|
111
|
+
* **fallback**, used when no checkpoint id is available from either source
|
|
112
|
+
* (the `workspace.spriteName`-only path below). When there is no sprite id
|
|
113
|
+
* to restore at all (no `output`, no `WeakMap` record, no
|
|
114
|
+
* `workspace.spriteName`), `rollback()` degrades with an explicit,
|
|
115
|
+
* commented no-op return — the same pattern
|
|
116
|
+
* `../../lexicons/aws/src/components/host-delivery.ts`'s `code-deploy`
|
|
117
|
+
* rollback uses (`if (!deploymentId) return;`) — rather than throwing.
|
|
118
|
+
* **Before #1944**, the Temporal durable path (`run` and `rollback`
|
|
119
|
+
* executing as separate Activities, each rebuilding `input` fresh via
|
|
120
|
+
* `resolveStepInput`) never gave the `WeakMap` a hit, so a fresh sprite's
|
|
121
|
+
* rollback there silently degraded to that no-op; passing `output` through
|
|
122
|
+
* (this revision) closes that gap directly, without redesigning the
|
|
123
|
+
* component-op wire format — see #1944's PR description for why this was
|
|
124
|
+
* chosen over the "make the degrade loud" alternative. The generated
|
|
125
|
+
* workflow's saga-unwind loop (`lexicons/temporal/src/component-op/
|
|
126
|
+
* serializer.ts`) also no longer swallows a rollback failure silently
|
|
127
|
+
* (any capability's, not just this one) — it now logs it and surfaces it
|
|
128
|
+
* via a `RollbackFailed` search attribute, defense in depth for a rollback
|
|
129
|
+
* failure unrelated to identity (e.g. the sprite backend itself erroring).
|
|
130
|
+
* - **Destroy vs. leave-alive.** `run()` destroys a freshly created (not
|
|
131
|
+
* `workspace.spriteName`-reused) sprite only when `turn.status ===
|
|
132
|
+
* "completed"`. An ordinary failed turn leaves the sprite alive — it
|
|
133
|
+
* returned normally, so no saga rollback runs, but the failure is exactly
|
|
134
|
+
* when a caller (or this module's own tests) most wants to inspect the
|
|
135
|
+
* sprite or explicitly call `rollback()` to restore it, per this issue's
|
|
136
|
+
* acceptance criteria.
|
|
137
|
+
*
|
|
138
|
+
* **Still open, left for later phases:**
|
|
139
|
+
* - Runtime invocation inside the sprite — `buildRuntimeCommand` below picks
|
|
140
|
+
* a real one-shot CLI invocation for each known `Agent.runtime` value,
|
|
141
|
+
* reading the staged prompt file; whether the sprite image already carries
|
|
142
|
+
* that CLI (vs. `run()` needing a setup step) is unresolved. `RunAgentInput`
|
|
143
|
+
* (frozen by #1941) has no separate `runtime` field, so `input.agent`
|
|
144
|
+
* doubles as the runtime selector for phase 2 — an unrecognized value is
|
|
145
|
+
* passed through verbatim as a literal command (a documented escape hatch
|
|
146
|
+
* for a custom binary already present in the image, and how this module's
|
|
147
|
+
* own tests exercise a scripted failure against the offline fake). Real
|
|
148
|
+
* `agent` name/id -> `Agent.runtime` resolution against fountain's
|
|
149
|
+
* `/api/agents` (`resolveAgentId`, `lexicons/fountain/.../fountain-run.ts`)
|
|
150
|
+
* is out of this issue's scope (and would need a network call the offline
|
|
151
|
+
* path forbids).
|
|
152
|
+
* - Output artifact encoding — `run()` reads one conventional path
|
|
153
|
+
* (`/work/output`) as the sole `artifacts.files` entry when present;
|
|
154
|
+
* individual `spriteReadFile` calls per changed path vs. one tarred
|
|
155
|
+
* snapshot folded into a `BuildArchiveManifest` entry (./build-archive.ts)
|
|
156
|
+
* remains an open question, as does `artifacts.diff`, which `run()` never
|
|
157
|
+
* populates (the real Sprites API has no built-in diff endpoint).
|
|
158
|
+
* - Interrupted turns — `turn.status: "interrupted"` stays in the type but
|
|
159
|
+
* is unreachable from this implementation: any `sprites.exec` rejection
|
|
160
|
+
* (including one caused by an aborted signal) propagates as a genuine
|
|
161
|
+
* `run()` failure rather than being classified as an interrupted turn.
|
|
162
|
+
* Distinguishing "deadline hit mid-run" from "the sprite backend errored"
|
|
163
|
+
* well enough to surface `"interrupted"` safely is left for #1944.
|
|
164
|
+
*
|
|
165
|
+
* **#1943 (this revision) resolved the transcript-hash basis and
|
|
166
|
+
* sign/verify-gate interop, closing #1941's open "transcript hash basis"
|
|
167
|
+
* question:**
|
|
168
|
+
* - **Hash basis.** `provenance.sourceRef`'s digest component is
|
|
169
|
+
* `sha256Digest(JSON.stringify(basis))` over a fixed-key-order object —
|
|
170
|
+
* `{ agent, promptDigest, images, status, exitCode, stdoutDigest,
|
|
171
|
+
* stderrDigest, artifacts, diffDigest }` — built fresh by
|
|
172
|
+
* `computeTranscriptDigest` below every call, never re-ordered from a
|
|
173
|
+
* caller-supplied object, so key order (and therefore the digest) never
|
|
174
|
+
* drifts by construction. Every free-text field (`prompt`, `stdout`,
|
|
175
|
+
* `stderr`, `diff`, each image's `data`) is folded in as its own
|
|
176
|
+
* `sha256Digest`, not embedded verbatim — this keeps a (possibly large,
|
|
177
|
+
* possibly secret-bearing) prompt or transcript out of the digest input's
|
|
178
|
+
* own byte stream while the digest remains exactly as sensitive to those
|
|
179
|
+
* bytes as embedding them would be, and it is *why* this module never
|
|
180
|
+
* needs to retain raw stdout/stderr past computing their digest inline
|
|
181
|
+
* (closing #1941's "is the raw transcript retained anywhere beyond the
|
|
182
|
+
* attested digest" sub-question: no, not even internally). `startedAt`/
|
|
183
|
+
* `endedAt` are deliberately excluded — including wall-clock time would
|
|
184
|
+
* make "the same turn" (same prompt, same exec outcome, same artifacts)
|
|
185
|
+
* hash differently on every real run, defeating the determinism property
|
|
186
|
+
* (`same turn -> same sourceRef`) a verifier actually needs: identity is
|
|
187
|
+
* about *what happened*, not *when*. `artifacts.files` is sorted by
|
|
188
|
+
* `path` before hashing so collection order (never semantically
|
|
189
|
+
* meaningful — there is exactly one conventional artifact today, see
|
|
190
|
+
* `OUTPUT_PATH`) can't perturb the digest.
|
|
191
|
+
* - **`sourceRef` folding.** `RunAgentInput.sourceRef`'s own doc comment
|
|
192
|
+
* promises it is "folded into the output's `provenance.sourceRef`
|
|
193
|
+
* alongside the transcript hash." Concretely: `sourceRef =
|
|
194
|
+
* input.sourceRef ? \`${input.sourceRef}@${transcriptDigest}\` :
|
|
195
|
+
* transcriptDigest` — an `"<sha>@sha256:<hex>"` shape when a source ref is
|
|
196
|
+
* known, reading like the `repo@sha256:...` convention already used
|
|
197
|
+
* throughout this codebase (./sign.ts, ./publish.ts's `uri`), or the bare
|
|
198
|
+
* `sha256:<hex>` transcript digest alone when it is not. `@` (not `:`,
|
|
199
|
+
* which `DockerBuildInput.sourceRef`'s own `"<sha>:<path>"` convention
|
|
200
|
+
* already uses for a different purpose) keeps the split unambiguous.
|
|
201
|
+
* `extractTranscriptDigest` below recovers the trailing
|
|
202
|
+
* `sha256:<hex>` deterministically regardless of what `input.sourceRef`
|
|
203
|
+
* contains, by anchoring on the fixed `sha256:[0-9a-f]{64}` pattern at the
|
|
204
|
+
* very end of the string.
|
|
205
|
+
* - **Attestation interop, decided.** `RunAgentOutput` gained one field,
|
|
206
|
+
* `attestationRef` — a `"<component>/run-agent@sha256:<hex>"` string
|
|
207
|
+
* already shaped like the digest-qualified `repo@sha256:...` reference
|
|
208
|
+
* `./sign.ts`'s `assertDigestRef`/`./verify.ts` already require, built
|
|
209
|
+
* from `ctx.component` and the transcript digest, so a `sign`/
|
|
210
|
+
* `attest-provenance`/`verify` step composed after `run-agent` wires
|
|
211
|
+
* `imageRef: "@RunAgent.attestationRef"` and needs **zero code changes**
|
|
212
|
+
* to any of those three verbs — resolving design point 4 of #1943 exactly
|
|
213
|
+
* as anticipated. The honest caveat, stated plainly rather than left
|
|
214
|
+
* implicit: `attestationRef` is *shaped* like an OCI digest reference, but
|
|
215
|
+
* is not by itself a real, registry-resolvable one — `run-agent`'s turn
|
|
216
|
+
* output is never pushed anywhere by this module. A deployment that wants
|
|
217
|
+
* a genuine `cosign sign`/`cosign verify` round trip against real
|
|
218
|
+
* Rekor/Fulcio needs a registry-backed publish step ahead of `sign`
|
|
219
|
+
* (mirroring `publish-image`'s `uri`, ./publish.ts) — out of this issue's
|
|
220
|
+
* scope, and not needed for the offline contract this issue asks for
|
|
221
|
+
* (injected `ProcessRunner`, no real `cosign`/registry ever touched).
|
|
222
|
+
* `toRunAgentArchiveEntry` below folds the same digest into a
|
|
223
|
+
* `BuildArchiveEntry` (`kind: "asset"`, ./build-archive.ts) per design
|
|
224
|
+
* point 2, for a caller building a full archive manifest.
|
|
225
|
+
* - **Predicate-type decision, made (not left open).** Reuses the existing
|
|
226
|
+
* `predicateType: "https://slsa.dev/provenance/v1"` unchanged — does
|
|
227
|
+
* *not* mint a `run-agent`-specific predicate type. `buildDefinition
|
|
228
|
+
* .buildType` is the field SLSA v1 actually designates for "what kind of
|
|
229
|
+
* recipe produced this" (`RUN_AGENT_BUILD_TYPE` below,
|
|
230
|
+
* `"https://chant.dev/agent-turn/v1"`, the same role
|
|
231
|
+
* `./sign.ts`'s `DEFAULT_BUILD_TYPE` plays for `docker-build`), so a
|
|
232
|
+
* second, parallel `predicateType` taxonomy would duplicate what
|
|
233
|
+
* `buildType` already discriminates. This is also what makes the "zero
|
|
234
|
+
* code changes to `verify.ts`" claim above literally true:
|
|
235
|
+
* `buildVerifyAttestationArgs` hardcodes `--type slsaprovenance1` — a
|
|
236
|
+
* minted `predicateType` would have broken that interop outright, forcing
|
|
237
|
+
* a `verify.ts` change this issue's design point 4 explicitly hoped to
|
|
238
|
+
* avoid.
|
|
239
|
+
* - **Fountain-supplied facts, honestly scoped.** `buildRunAgentProvenanceStatement`
|
|
240
|
+
* below folds `input.agent` into `externalParameters` (the one *declared*
|
|
241
|
+
* fact `RunAgentInput` actually carries today) and `spriteId`/
|
|
242
|
+
* `checkpointId`/`turn.status`/`turn.exitCode` into `internalParameters`
|
|
243
|
+
* (what actually happened). `model`/`runtime`/`Environment.networking_type`
|
|
244
|
+
* /`allowed_hosts` are not included — populating them honestly needs
|
|
245
|
+
* fountain `Agent`/`Environment` resolution against `/api/agents`, which
|
|
246
|
+
* this module's own doc comment already marks out of scope ("Real `agent`
|
|
247
|
+
* name/id -> `Agent.runtime` resolution ... is out of this issue's
|
|
248
|
+
* scope"). Adding those keys later is additive (merged into
|
|
249
|
+
* `externalParameters`, never replacing it), not a shape break.
|
|
250
|
+
*/
|
|
251
|
+
|
|
252
|
+
import { createHash } from "node:crypto";
|
|
253
|
+
import type { Capability, DeployContext } from "../capability";
|
|
254
|
+
import type { ProvenanceLink } from "./reproducibility";
|
|
255
|
+
import type { BuildArchiveEntry } from "./build-archive";
|
|
256
|
+
import { buildProvenanceStatement, type InTotoProvenanceStatement } from "./sign";
|
|
257
|
+
|
|
258
|
+
// ── fountain surface excerpts this input/output tracks ──────────────────────
|
|
259
|
+
|
|
260
|
+
/** fountain's `ImageInput` (`PromptRequest.images[]`) — a base64-encoded image attached to a prompt. */
|
|
261
|
+
export interface RunAgentImageInput {
|
|
262
|
+
/** Base64-encoded image bytes. */
|
|
263
|
+
data: string;
|
|
264
|
+
media_type: "image/png" | "image/jpeg" | "image/gif" | "image/webp";
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** fountain's `Agent.runtime` — the CLI the turn runs inside the sprite. */
|
|
268
|
+
export type RunAgentRuntime = "claude" | "codex" | "gemini" | "opencode";
|
|
269
|
+
|
|
270
|
+
// ── run-agent ─────────────────────────────────────────────────────────────
|
|
271
|
+
|
|
272
|
+
export interface RunAgentInput {
|
|
273
|
+
/** fountain `Agent` name or id — resolved like `fountainRun`'s `resolveAgentId` (`lexicons/fountain/src/op/activities/fountain-run.ts`) against `/api/agents`. */
|
|
274
|
+
agent: string;
|
|
275
|
+
task: {
|
|
276
|
+
prompt: string;
|
|
277
|
+
/** fountain's `ImageInput` shape (`PromptRequest.images`). */
|
|
278
|
+
images?: RunAgentImageInput[];
|
|
279
|
+
};
|
|
280
|
+
workspace: {
|
|
281
|
+
/** Reuse an existing sprite (warm start). Omit to create a fresh sprite for this turn. */
|
|
282
|
+
spriteName?: string;
|
|
283
|
+
/** Base image for a freshly created sprite. Ignored when reusing `spriteName`. */
|
|
284
|
+
image?: string;
|
|
285
|
+
/** Checkpoint comment for the pre-run checkpoint `rollback()` restores to. Default: `"pre-run"`. */
|
|
286
|
+
checkpointComment?: string;
|
|
287
|
+
};
|
|
288
|
+
/** Folded into the output's `provenance.sourceRef` alongside the transcript hash — same field name `DockerBuildInput.sourceRef` already uses (#614, ./build.ts). Omit when unknown; no `provenance` basis beyond the transcript hash in that case. */
|
|
289
|
+
sourceRef?: string;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/** Mirrors fountain's `Turn` shape (`status`/`exit_code`/`started_at`/`ended_at`), even though the turn itself runs on a chant-owned sprite rather than a fountain-managed `Sandbox`. */
|
|
293
|
+
export interface RunAgentTurn {
|
|
294
|
+
status: "completed" | "failed" | "interrupted";
|
|
295
|
+
exitCode: number | null;
|
|
296
|
+
startedAt: string;
|
|
297
|
+
endedAt: string | null;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** One artifact file the turn produced, content-addressed the same way a `BuildArchiveEntry` is (./build-archive.ts). */
|
|
301
|
+
export interface RunAgentArtifactFile {
|
|
302
|
+
path: string;
|
|
303
|
+
digest: string;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export interface RunAgentOutput {
|
|
307
|
+
/** The sprite this turn ran on — the same id `rollback()` restores. */
|
|
308
|
+
spriteId: string;
|
|
309
|
+
/** The pre-run checkpoint id — the same id `rollback()` restores to. */
|
|
310
|
+
checkpointId: string;
|
|
311
|
+
turn: RunAgentTurn;
|
|
312
|
+
artifacts: {
|
|
313
|
+
files: RunAgentArtifactFile[];
|
|
314
|
+
/** Unified diff of the workspace against its pre-run checkpoint, when applicable. */
|
|
315
|
+
diff?: string;
|
|
316
|
+
};
|
|
317
|
+
/** #614's shape (./reproducibility.ts) — `sourceRef` is the prompt/transcript hash, folded with `input.sourceRef` when supplied (see this module's doc comment, "sourceRef folding"). */
|
|
318
|
+
provenance: ProvenanceLink;
|
|
319
|
+
/**
|
|
320
|
+
* A `"<component>/run-agent@sha256:<hex>"` reference, digest-qualified the
|
|
321
|
+
* same shape `./sign.ts`'s `assertDigestRef`/`./verify.ts` require (#1943)
|
|
322
|
+
* — wire a `sign`/`attest-provenance`/`verify` step composed after
|
|
323
|
+
* `run-agent` with `imageRef: "@RunAgent.attestationRef"` and none of those
|
|
324
|
+
* three verbs need any code change. See this module's doc comment,
|
|
325
|
+
* "Attestation interop, decided" for the honest caveat (shaped like an OCI
|
|
326
|
+
* digest reference, not itself a registry-resolvable one) and
|
|
327
|
+
* `extractTranscriptDigest`/`toRunAgentArchiveEntry`/
|
|
328
|
+
* `buildRunAgentProvenanceStatement` below for the rest of the interop
|
|
329
|
+
* surface built on top of it.
|
|
330
|
+
*/
|
|
331
|
+
attestationRef: string;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// ── SpriteActivities: the injectable sprite-lifecycle seam ─────────────────
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* The subset of `lexicons/fly/src/op/activities/sprites.ts` +
|
|
338
|
+
* `sprite-fs.ts`'s activity contracts `run-agent` needs, restated
|
|
339
|
+
* structurally here (rather than imported) so this module carries no hard
|
|
340
|
+
* package dependency on `@intentius/chant-lexicon-fly` — the same
|
|
341
|
+
* injectable-seam shape ./cloud-executor.ts's `CloudExecutor`/`DockerClient`
|
|
342
|
+
* use to keep ./build.ts's `docker-build` testable with no real `docker`
|
|
343
|
+
* daemon. A real implementation (#1942) is structurally compatible with
|
|
344
|
+
* `spriteCreate`/`spriteCheckpoint`/`spriteExec`/`spriteRestore`/
|
|
345
|
+
* `spriteDestroy`/`spriteWriteFile`/`spriteReadFile`'s existing signatures —
|
|
346
|
+
* adapting them is a thin wrapper, not a rewrite.
|
|
347
|
+
*/
|
|
348
|
+
export interface SpriteActivities {
|
|
349
|
+
create(
|
|
350
|
+
args: { name: string; image?: string },
|
|
351
|
+
signal?: AbortSignal,
|
|
352
|
+
): Promise<{ id: string; url: string }>;
|
|
353
|
+
checkpoint(
|
|
354
|
+
args: { id: string; comment?: string },
|
|
355
|
+
signal?: AbortSignal,
|
|
356
|
+
): Promise<{ checkpointId: string }>;
|
|
357
|
+
exec(
|
|
358
|
+
args: { id: string; cmd: string; timeoutMs?: number },
|
|
359
|
+
signal?: AbortSignal,
|
|
360
|
+
): Promise<{ stdout: string; stderr: string; exitCode: number }>;
|
|
361
|
+
restore(
|
|
362
|
+
args: { id: string; checkpoint?: string; comment?: string },
|
|
363
|
+
signal?: AbortSignal,
|
|
364
|
+
): Promise<void>;
|
|
365
|
+
destroy(args: { id: string }, signal?: AbortSignal): Promise<void>;
|
|
366
|
+
writeFile(
|
|
367
|
+
args: { id: string; path: string; content: string; mkdir?: boolean },
|
|
368
|
+
signal?: AbortSignal,
|
|
369
|
+
): Promise<void>;
|
|
370
|
+
readFile(
|
|
371
|
+
args: { id: string; path: string },
|
|
372
|
+
signal?: AbortSignal,
|
|
373
|
+
): Promise<{ content: string }>;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Thrown by `defaultSpriteActivities()`'s methods when a caller never injects
|
|
378
|
+
* a real (or fake) `SpriteActivities` — the default remains this
|
|
379
|
+
* not-wired-yet placeholder even after #1942 wires `run()`/`rollback()`'s
|
|
380
|
+
* sequencing logic, because *some* concrete backend still has to be supplied
|
|
381
|
+
* for that logic to have anything to call. `lexicons/fly/src/components/
|
|
382
|
+
* run-agent.ts`'s adapter (the real backend, over this lexicon's own sprite
|
|
383
|
+
* lifecycle activities) or the offline `sprites-fake`/`sprites-emulator` path
|
|
384
|
+
* are what a caller injects instead. A caller that supplies its own
|
|
385
|
+
* `SpriteActivities` never hits this class.
|
|
386
|
+
*/
|
|
387
|
+
export class SpriteActivitiesNotWiredError extends Error {
|
|
388
|
+
constructor(public readonly method: keyof SpriteActivities) {
|
|
389
|
+
super(
|
|
390
|
+
`SpriteActivities.${method}: not wired to a real sprite backend yet — sprite lifecycle wiring is #1942 ` +
|
|
391
|
+
`(epic #1564 phase 2). Inject a real or fake ("sprites-fake"/"sprites-emulator") implementation to ` +
|
|
392
|
+
`exercise "run-agent" end to end.`,
|
|
393
|
+
);
|
|
394
|
+
this.name = "SpriteActivitiesNotWiredError";
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/** Phase 1's placeholder default: every method throws `SpriteActivitiesNotWiredError`. See that class's doc comment. */
|
|
399
|
+
export function defaultSpriteActivities(): SpriteActivities {
|
|
400
|
+
const notWired =
|
|
401
|
+
<M extends keyof SpriteActivities>(method: M) =>
|
|
402
|
+
async (): Promise<never> => {
|
|
403
|
+
throw new SpriteActivitiesNotWiredError(method);
|
|
404
|
+
};
|
|
405
|
+
return {
|
|
406
|
+
create: notWired("create"),
|
|
407
|
+
checkpoint: notWired("checkpoint"),
|
|
408
|
+
exec: notWired("exec"),
|
|
409
|
+
restore: notWired("restore"),
|
|
410
|
+
destroy: notWired("destroy"),
|
|
411
|
+
writeFile: notWired("writeFile"),
|
|
412
|
+
readFile: notWired("readFile"),
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// ── capability ───────────────────────────────────────────────────────────
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Build the `run-agent` capability. `sprites` is the injectable
|
|
420
|
+
* `SpriteActivities` seam (default: `defaultSpriteActivities()`, the
|
|
421
|
+
* not-wired-yet placeholder — see that function's doc comment) so unit tests
|
|
422
|
+
* never touch a real/emulated sprite, mirroring `createDockerBuildCapability`'s
|
|
423
|
+
* `executor: CloudExecutor` parameter (./build.ts). The fly lexicon's
|
|
424
|
+
* `flyRunAgentCapability` (`lexicons/fly/src/components/run-agent.ts`) is
|
|
425
|
+
* this same factory called with its real `SpriteActivities` adapter.
|
|
426
|
+
*
|
|
427
|
+
* `run()`: `sprites.create` (skipped when reusing `workspace.spriteName`) ->
|
|
428
|
+
* `sprites.checkpoint` (`comment: workspace.checkpointComment ?? "pre-run"`)
|
|
429
|
+
* -> `sprites.writeFile` to stage the prompt -> `sprites.exec` the runtime
|
|
430
|
+
* command (`buildRuntimeCommand`) -> `sprites.readFile` to collect the sole
|
|
431
|
+
* artifact -> `sprites.destroy`, only for a freshly created sprite whose turn
|
|
432
|
+
* completed. See this module's doc comment for the exec-throw resolution,
|
|
433
|
+
* the destroy-vs-leave-alive rule, and the rollback-identity design.
|
|
434
|
+
*
|
|
435
|
+
* `rollback()`: `sprites.restore({ id: spriteId, checkpoint: checkpointId })`
|
|
436
|
+
* — the sole compensation, restoring to the exact pre-run checkpoint `run()`
|
|
437
|
+
* recorded (see this module's doc comment, "Rollback identity"). Prefers the
|
|
438
|
+
* `output` parameter's `spriteId`/`checkpointId` (the durable-safe channel,
|
|
439
|
+
* #1944) over the in-process `WeakMap`, falls back to
|
|
440
|
+
* `sprites.restore({ id: spriteId, comment })` only when neither source has a
|
|
441
|
+
* checkpoint id, and degrades to an explicit no-op when there is no sprite id
|
|
442
|
+
* to restore at all. No hand-written inverse action otherwise; "the
|
|
443
|
+
* environment is the transaction."
|
|
444
|
+
*
|
|
445
|
+
* `rollbackPolicy: "native"` is set explicitly (not left to
|
|
446
|
+
* ../capability.ts's `rollback`-method inference) so the design commitment
|
|
447
|
+
* reads directly off this capability's declaration: a mutating `run-agent`
|
|
448
|
+
* step never needs a `noRollback` opt-out for COMP003
|
|
449
|
+
* (../lint/rules/comp/comp003-mutating-no-rollback.ts).
|
|
450
|
+
*/
|
|
451
|
+
export function createRunAgentCapability(
|
|
452
|
+
sprites: SpriteActivities = defaultSpriteActivities(),
|
|
453
|
+
): Capability<RunAgentInput, RunAgentOutput> {
|
|
454
|
+
// Keyed by the exact `input` object `run()` was called with — a fallback
|
|
455
|
+
// for a caller that never threads `output` through to `rollback()` (see
|
|
456
|
+
// this module's doc comment, "Rollback identity"). The durable-safe path
|
|
457
|
+
// is `output.spriteId`/`output.checkpointId`, already part of
|
|
458
|
+
// `RunAgentOutput` (#1943) and threaded by every current caller (#1944).
|
|
459
|
+
const stateByInput = new WeakMap<RunAgentInput, RunAgentRunState>();
|
|
460
|
+
|
|
461
|
+
return {
|
|
462
|
+
kind: "run-agent",
|
|
463
|
+
rollbackPolicy: "native",
|
|
464
|
+
async run(ctx, input): Promise<RunAgentOutput> {
|
|
465
|
+
const reused = Boolean(input.workspace.spriteName);
|
|
466
|
+
const spriteId = input.workspace.spriteName ?? generateSpriteName(ctx);
|
|
467
|
+
if (!reused) {
|
|
468
|
+
await sprites.create({ name: spriteId, image: input.workspace.image });
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const checkpointComment = input.workspace.checkpointComment ?? "pre-run";
|
|
472
|
+
const { checkpointId } = await sprites.checkpoint({ id: spriteId, comment: checkpointComment });
|
|
473
|
+
// Recorded as early as possible: even if a later step throws (a genuine
|
|
474
|
+
// infra failure, triggering saga rollback), `rollback()` still finds
|
|
475
|
+
// the sprite id and exact checkpoint it needs to restore.
|
|
476
|
+
stateByInput.set(input, { spriteId, checkpointId });
|
|
477
|
+
|
|
478
|
+
await sprites.writeFile({ id: spriteId, path: PROMPT_PATH, content: input.task.prompt, mkdir: true });
|
|
479
|
+
|
|
480
|
+
const startedAt = new Date().toISOString();
|
|
481
|
+
// No try/catch here by design (see this module's doc comment, "The
|
|
482
|
+
// exec-throw finding"): `sprites.exec` resolves with `exitCode` for any
|
|
483
|
+
// ordinary command outcome and rejects only for a genuine infra
|
|
484
|
+
// failure, which should propagate and trigger saga rollback.
|
|
485
|
+
const execResult = await sprites.exec({ id: spriteId, cmd: buildRuntimeCommand(input.agent) });
|
|
486
|
+
const turn: RunAgentTurn = {
|
|
487
|
+
status: execResult.exitCode === 0 ? "completed" : "failed",
|
|
488
|
+
exitCode: execResult.exitCode,
|
|
489
|
+
startedAt,
|
|
490
|
+
endedAt: new Date().toISOString(),
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
const artifacts = await collectArtifacts(sprites, spriteId);
|
|
494
|
+
|
|
495
|
+
// Destroy only on a normal-return success. A failed turn (status
|
|
496
|
+
// "failed") leaves the sprite alive — no saga rollback runs for it
|
|
497
|
+
// (run() returned, it didn't throw), so this is the caller's own
|
|
498
|
+
// window to inspect the sprite or explicitly call rollback().
|
|
499
|
+
if (!reused && turn.status === "completed") {
|
|
500
|
+
await sprites.destroy({ id: spriteId });
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// #1943: the transcript digest — see this module's doc comment ("Hash
|
|
504
|
+
// basis") for the exact fields/order this is computed over.
|
|
505
|
+
// execResult.stdout/stderr never outlive this call: they are folded
|
|
506
|
+
// into the digest right here and discarded, never stored on `turn` or
|
|
507
|
+
// anywhere else in `RunAgentOutput`.
|
|
508
|
+
const transcriptDigest = computeTranscriptDigest({
|
|
509
|
+
agent: input.agent,
|
|
510
|
+
prompt: input.task.prompt,
|
|
511
|
+
images: input.task.images,
|
|
512
|
+
turn,
|
|
513
|
+
stdout: execResult.stdout,
|
|
514
|
+
stderr: execResult.stderr,
|
|
515
|
+
artifacts,
|
|
516
|
+
});
|
|
517
|
+
const sourceRef = input.sourceRef ? `${input.sourceRef}@${transcriptDigest}` : transcriptDigest;
|
|
518
|
+
const provenance: ProvenanceLink = {
|
|
519
|
+
sourceRef,
|
|
520
|
+
artifactDigest: artifacts.files[0]?.digest ?? EMPTY_DIGEST,
|
|
521
|
+
};
|
|
522
|
+
const attestationRef = `${ctx.component}/run-agent@${transcriptDigest}`;
|
|
523
|
+
|
|
524
|
+
return { spriteId, checkpointId, turn, artifacts, provenance, attestationRef };
|
|
525
|
+
},
|
|
526
|
+
async rollback(_ctx, input, output): Promise<void> {
|
|
527
|
+
// The durable-safe channel first (#1944): `output` is the exact value
|
|
528
|
+
// this step's own `run()` returned, threaded through by every current
|
|
529
|
+
// caller (../driver.ts locally, lexicons/temporal/src/component-op/
|
|
530
|
+
// {activities,serializer}.ts across the Activity boundary) — it
|
|
531
|
+
// survives even when `rollback()` is called with a freshly-rebuilt
|
|
532
|
+
// `input` object the in-process WeakMap below has never seen. Falls
|
|
533
|
+
// back to the WeakMap for a caller that predates/never threads
|
|
534
|
+
// `output` (see this module's doc comment, "Rollback identity").
|
|
535
|
+
const state = stateByInput.get(input);
|
|
536
|
+
const spriteId = output?.spriteId ?? state?.spriteId ?? input.workspace.spriteName;
|
|
537
|
+
if (!spriteId) {
|
|
538
|
+
// No identity from any source (output, in-process record, or
|
|
539
|
+
// "workspace.spriteName"): there is nothing to identify which sprite
|
|
540
|
+
// to restore. Degrade explicitly rather than throwing into a
|
|
541
|
+
// swallowed catch, the same pattern
|
|
542
|
+
// ../../lexicons/aws/src/components/host-delivery.ts's code-deploy
|
|
543
|
+
// rollback uses (`if (!deploymentId) return;`).
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
// Restore the exact pre-run checkpoint when either source recorded
|
|
547
|
+
// one — wins over comment (see "Rollback identity"), and is the only
|
|
548
|
+
// safe choice on a reused sprite where two runs can share the same
|
|
549
|
+
// default "pre-run" comment.
|
|
550
|
+
const checkpointId = output?.checkpointId ?? state?.checkpointId;
|
|
551
|
+
if (checkpointId) {
|
|
552
|
+
await sprites.restore({ id: spriteId, checkpoint: checkpointId });
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
// Fallback: no checkpoint id from either source (e.g.
|
|
556
|
+
// workspace.spriteName-only, no output, no in-process run() record) —
|
|
557
|
+
// resolve by comment, same as before.
|
|
558
|
+
const comment = input.workspace.checkpointComment ?? "pre-run";
|
|
559
|
+
await sprites.restore({ id: spriteId, comment });
|
|
560
|
+
},
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// ── run() helpers ────────────────────────────────────────────────────────
|
|
565
|
+
|
|
566
|
+
/** The conventional path `run()` writes the staged prompt to and reads the sole artifact from — the same `/work/*` convention `examples/sprites-agent-task/ops/agent-task.op.ts`'s Stage/Collect phases use. */
|
|
567
|
+
const PROMPT_PATH = "/work/prompt";
|
|
568
|
+
const OUTPUT_PATH = "/work/output";
|
|
569
|
+
|
|
570
|
+
/** `sha256:<hex>` over a string, prefixed the same way ./build-archive.ts's `contentDigest` and ./build.ts's zip/jar digests are. */
|
|
571
|
+
function sha256Digest(content: string): string {
|
|
572
|
+
return `sha256:${createHash("sha256").update(content, "utf8").digest("hex")}`;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const EMPTY_DIGEST = sha256Digest("");
|
|
576
|
+
|
|
577
|
+
// ── #1943: transcript hash + attestation interop ────────────────────────────
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* The exact, fixed-key-order shape `computeTranscriptDigest` hashes — see
|
|
581
|
+
* this module's doc comment ("Hash basis") for the full rationale. Every
|
|
582
|
+
* free-text field is its own digest, never embedded verbatim.
|
|
583
|
+
*/
|
|
584
|
+
interface TranscriptBasis {
|
|
585
|
+
agent: string;
|
|
586
|
+
promptDigest: string;
|
|
587
|
+
images: Array<{ mediaType: string; digest: string }>;
|
|
588
|
+
status: RunAgentTurn["status"];
|
|
589
|
+
exitCode: number | null;
|
|
590
|
+
stdoutDigest: string;
|
|
591
|
+
stderrDigest: string;
|
|
592
|
+
artifacts: Array<{ path: string; digest: string }>;
|
|
593
|
+
diffDigest: string;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Compute the transcript digest that becomes (the digest component of)
|
|
598
|
+
* `provenance.sourceRef` and `attestationRef` — see this module's doc
|
|
599
|
+
* comment, "Hash basis," for the precise field list/order and the rationale
|
|
600
|
+
* for hashing rather than embedding each free-text field. Exported so a
|
|
601
|
+
* caller (or a test) can independently recompute/verify the same digest from
|
|
602
|
+
* the same run() inputs/outputs, without reaching into this module's private
|
|
603
|
+
* `run()` closure.
|
|
604
|
+
*/
|
|
605
|
+
export function computeTranscriptDigest(params: {
|
|
606
|
+
agent: string;
|
|
607
|
+
prompt: string;
|
|
608
|
+
images?: RunAgentImageInput[];
|
|
609
|
+
turn: RunAgentTurn;
|
|
610
|
+
stdout: string;
|
|
611
|
+
stderr: string;
|
|
612
|
+
artifacts: RunAgentOutput["artifacts"];
|
|
613
|
+
}): string {
|
|
614
|
+
const basis: TranscriptBasis = {
|
|
615
|
+
agent: params.agent,
|
|
616
|
+
promptDigest: sha256Digest(params.prompt),
|
|
617
|
+
images: (params.images ?? []).map((img) => ({ mediaType: img.media_type, digest: sha256Digest(img.data) })),
|
|
618
|
+
status: params.turn.status,
|
|
619
|
+
exitCode: params.turn.exitCode,
|
|
620
|
+
stdoutDigest: sha256Digest(params.stdout),
|
|
621
|
+
stderrDigest: sha256Digest(params.stderr),
|
|
622
|
+
artifacts: [...params.artifacts.files]
|
|
623
|
+
// Code-point ordering, not localeCompare: the digest must be identical
|
|
624
|
+
// across machines regardless of ICU/locale configuration.
|
|
625
|
+
.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0))
|
|
626
|
+
.map((f) => ({ path: f.path, digest: f.digest })),
|
|
627
|
+
diffDigest: sha256Digest(params.artifacts.diff ?? ""),
|
|
628
|
+
};
|
|
629
|
+
return sha256Digest(JSON.stringify(basis));
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/** The trailing `sha256:<hex>` transcript digest a `provenance.sourceRef`/`attestationRef` ends in — see this module's doc comment, "sourceRef folding." */
|
|
633
|
+
const TRANSCRIPT_DIGEST_PATTERN = /sha256:[0-9a-f]{64}$/i;
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Recover the bare transcript digest (`sha256:<hex>`) from a
|
|
637
|
+
* `provenance.sourceRef` or `attestationRef` string, regardless of what (if
|
|
638
|
+
* anything) precedes it — see this module's doc comment, "sourceRef
|
|
639
|
+
* folding," for why anchoring on the fixed `sha256:[0-9a-f]{64}` suffix
|
|
640
|
+
* pattern is unambiguous even when `input.sourceRef` itself contains `@`/`:`.
|
|
641
|
+
*/
|
|
642
|
+
export function extractTranscriptDigest(sourceRef: string): string {
|
|
643
|
+
const match = TRANSCRIPT_DIGEST_PATTERN.exec(sourceRef);
|
|
644
|
+
if (!match) {
|
|
645
|
+
throw new Error(
|
|
646
|
+
`extractTranscriptDigest: "${sourceRef}" does not end in a "sha256:<hex>" transcript digest`,
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
return match[0];
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* `run-agent`'s SLSA `buildDefinition.buildType` — the recipe-kind URI this
|
|
654
|
+
* module's doc comment ("Predicate-type decision, made") settles on in place
|
|
655
|
+
* of minting a new `predicateType`. Mirrors ./sign.ts's own
|
|
656
|
+
* `DEFAULT_BUILD_TYPE` for `docker-build`.
|
|
657
|
+
*/
|
|
658
|
+
export const RUN_AGENT_BUILD_TYPE = "https://chant.dev/agent-turn/v1";
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Fold a `RunAgentOutput` into a `BuildArchiveEntry` (#1943 design point 2,
|
|
662
|
+
* ./build-archive.ts) — `kind: "asset"`, content-addressed by the same
|
|
663
|
+
* transcript digest `attestationRef` carries. Returns a bare entry (no
|
|
664
|
+
* `reproducibility` assigned); pass it through `addArchiveEntry` for the
|
|
665
|
+
* kind-appropriate `"best-effort"` default (./reproducibility.ts), the same
|
|
666
|
+
* as any other `asset` entry.
|
|
667
|
+
*/
|
|
668
|
+
export function toRunAgentArchiveEntry(component: string, output: RunAgentOutput): BuildArchiveEntry {
|
|
669
|
+
return {
|
|
670
|
+
kind: "asset",
|
|
671
|
+
path: `run-agent/${component}/${output.spriteId}-turn.json`,
|
|
672
|
+
digest: extractTranscriptDigest(output.provenance.sourceRef),
|
|
673
|
+
mediaType: "application/json",
|
|
674
|
+
provenance: output.provenance,
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* Build the in-toto SLSA provenance statement for a completed turn, reusing
|
|
680
|
+
* ./sign.ts's `buildProvenanceStatement` unmodified in behavior for every
|
|
681
|
+
* existing (image) caller — `predicateType` stays
|
|
682
|
+
* `"https://slsa.dev/provenance/v1"`, only `buildType` and the
|
|
683
|
+
* `external`/`internalParameters` merge fields (#1943's minimal, documented
|
|
684
|
+
* extension to `BuildProvenanceStatementInput`) differ. See this module's
|
|
685
|
+
* doc comment, "Fountain-supplied facts, honestly scoped," for exactly what
|
|
686
|
+
* is (and is not yet) folded in. The returned statement's `subject` is
|
|
687
|
+
* `output.attestationRef` — sign+attach it the same way `attest-provenance`
|
|
688
|
+
* already does for any other digest-qualified reference.
|
|
689
|
+
*/
|
|
690
|
+
export function buildRunAgentProvenanceStatement(
|
|
691
|
+
input: RunAgentInput,
|
|
692
|
+
output: RunAgentOutput,
|
|
693
|
+
builderId: string,
|
|
694
|
+
opts?: { finishedOn?: string; invocationId?: string },
|
|
695
|
+
): InTotoProvenanceStatement {
|
|
696
|
+
return buildProvenanceStatement({
|
|
697
|
+
imageRef: output.attestationRef,
|
|
698
|
+
provenance: output.provenance,
|
|
699
|
+
builderId,
|
|
700
|
+
buildType: RUN_AGENT_BUILD_TYPE,
|
|
701
|
+
externalParameters: { agent: input.agent },
|
|
702
|
+
internalParameters: {
|
|
703
|
+
spriteId: output.spriteId,
|
|
704
|
+
checkpointId: output.checkpointId,
|
|
705
|
+
turnStatus: output.turn.status,
|
|
706
|
+
turnExitCode: output.turn.exitCode,
|
|
707
|
+
},
|
|
708
|
+
finishedOn: opts?.finishedOn ?? output.turn.endedAt ?? undefined,
|
|
709
|
+
invocationId: opts?.invocationId,
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* Map `RunAgentInput.agent` to the one-shot, non-interactive command that
|
|
715
|
+
* reads the staged prompt (`PROMPT_PATH`) and runs it inside the sprite. Each
|
|
716
|
+
* known `RunAgentRuntime` gets its real CLI invocation; any other value is
|
|
717
|
+
* passed through verbatim as a literal command — a real-world escape hatch
|
|
718
|
+
* for a custom binary already present in the sprite image, and how this
|
|
719
|
+
* module's own tests drive a scripted failure/success against the offline
|
|
720
|
+
* fake without needing a real CLI. See this module's doc comment ("Still
|
|
721
|
+
* open, left for later phases") for why no fountain resolution happens here.
|
|
722
|
+
*/
|
|
723
|
+
export function buildRuntimeCommand(agent: string): string {
|
|
724
|
+
switch (agent as RunAgentRuntime) {
|
|
725
|
+
case "claude":
|
|
726
|
+
return `claude -p "$(cat ${PROMPT_PATH})" --output-format json`;
|
|
727
|
+
case "codex":
|
|
728
|
+
return `codex exec "$(cat ${PROMPT_PATH})"`;
|
|
729
|
+
case "gemini":
|
|
730
|
+
return `gemini -p "$(cat ${PROMPT_PATH})"`;
|
|
731
|
+
case "opencode":
|
|
732
|
+
return `opencode run "$(cat ${PROMPT_PATH})"`;
|
|
733
|
+
default:
|
|
734
|
+
return agent;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/** Deterministic-enough default sprite name when `workspace.spriteName` is omitted — not required to be reproducible across calls (see this module's doc comment on rollback identity: the `WeakMap` is what makes rollback work, not the name shape). */
|
|
739
|
+
function generateSpriteName(ctx: DeployContext): string {
|
|
740
|
+
const suffix = Math.random().toString(36).slice(2, 10);
|
|
741
|
+
return `run-agent-${ctx.component}-${suffix}`.replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/**
|
|
745
|
+
* Match the "not found" error shape `lexicons/fly/src/op/activities/
|
|
746
|
+
* sprite-fs.ts`'s `spriteReadFile` throws for a 404 specifically (`sprite
|
|
747
|
+
* <id> read <path>: not found`) — distinct from that same module's >=300
|
|
748
|
+
* `"... failed (<status>): ..."` shape, which is a genuine infra failure. The
|
|
749
|
+
* match is narrow on purpose, mirroring how `lexicons/fly/src/components/
|
|
750
|
+
* run-agent.ts`'s `parseSpriteExecFailure` only reclassifies the one thrown
|
|
751
|
+
* shape it recognizes and lets everything else through.
|
|
752
|
+
*/
|
|
753
|
+
function isArtifactNotFoundError(err: unknown): boolean {
|
|
754
|
+
return err instanceof Error && /^sprite .+ read .+: not found$/.test(err.message);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* Read `OUTPUT_PATH` as the turn's sole artifact, when present. Only a
|
|
759
|
+
* "not found" read (no artifact was ever written — e.g. a failed turn that
|
|
760
|
+
* never got that far) is treated as "no artifact"; any other error (a
|
|
761
|
+
* genuine transport/infra failure, or a real "failed (<status>)" from the
|
|
762
|
+
* sprite filesystem API) is rethrown so it propagates as a `run()` rejection
|
|
763
|
+
* and triggers saga rollback, instead of being silently swallowed into an
|
|
764
|
+
* empty `artifacts.files`.
|
|
765
|
+
*/
|
|
766
|
+
async function collectArtifacts(
|
|
767
|
+
sprites: SpriteActivities,
|
|
768
|
+
spriteId: string,
|
|
769
|
+
): Promise<RunAgentOutput["artifacts"]> {
|
|
770
|
+
try {
|
|
771
|
+
const { content } = await sprites.readFile({ id: spriteId, path: OUTPUT_PATH });
|
|
772
|
+
return { files: [{ path: OUTPUT_PATH, digest: sha256Digest(content) }] };
|
|
773
|
+
} catch (err) {
|
|
774
|
+
if (isArtifactNotFoundError(err)) return { files: [] };
|
|
775
|
+
throw err;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
/** Per-`input` record of what `run()` did, so `rollback()` (called with the same `input` object — see this module's doc comment) can recover the sprite id and exact pre-run checkpoint id without any persisted state. */
|
|
780
|
+
interface RunAgentRunState {
|
|
781
|
+
spriteId: string;
|
|
782
|
+
checkpointId: string;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
/** Default `run-agent` capability, backed by the not-wired-yet placeholder `SpriteActivities`. */
|
|
786
|
+
export const runAgentCapability: Capability<RunAgentInput, RunAgentOutput> = createRunAgentCapability();
|