@intentius/chant 0.100.0 → 0.102.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/agents/checks.d.ts.map +1 -1
- package/dist/attrref.d.ts.map +1 -1
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/audit/nginx.d.ts.map +1 -1
- package/dist/audit/prior-art.d.ts +48 -0
- package/dist/audit/prior-art.d.ts.map +1 -1
- package/dist/behaviour-kinds.d.ts.map +1 -1
- package/dist/build.d.ts +6 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/builder.d.ts.map +1 -1
- package/dist/cli/build-options.d.ts +2 -0
- package/dist/cli/build-options.d.ts.map +1 -1
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/carve-emit.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon.d.ts +16 -0
- package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/import.d.ts +8 -0
- package/dist/cli/commands/import.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/init.d.ts +19 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/onboard.d.ts +9 -0
- package/dist/cli/commands/onboard.d.ts.map +1 -1
- package/dist/cli/commands/pinned-upgrade.d.ts +19 -5
- package/dist/cli/commands/pinned-upgrade.d.ts.map +1 -1
- package/dist/cli/handlers/dev.d.ts.map +1 -1
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/misc.d.ts.map +1 -1
- package/dist/cli/handlers/operator.d.ts +9 -0
- package/dist/cli/handlers/operator.d.ts.map +1 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/lsp/server.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/server.d.ts.map +1 -1
- package/dist/cli/mcp/tools/build.d.ts.map +1 -1
- package/dist/cli/mcp/tools/composites.d.ts.map +1 -1
- package/dist/cli/mcp/tools/explain.d.ts.map +1 -1
- package/dist/cli/mcp/tools/import.d.ts.map +1 -1
- package/dist/cli/mcp/tools/lint.d.ts.map +1 -1
- package/dist/cli/mcp/tools/scaffold.d.ts.map +1 -1
- package/dist/cli/mcp/tools/search.d.ts.map +1 -1
- package/dist/cli/mcp/workspace-tools.d.ts +9 -1
- package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +33 -2
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/fetch.d.ts.map +1 -1
- package/dist/codegen/naming.d.ts.map +1 -1
- package/dist/codegen/pinned-upgrade.d.ts +67 -3
- package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
- package/dist/codegen/registry.d.ts.map +1 -1
- package/dist/components/capability-plugin-loader.d.ts.map +1 -1
- package/dist/components/capability.d.ts.map +1 -1
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/fan-out.d.ts.map +1 -1
- package/dist/components/starter-plugin.d.ts.map +1 -1
- package/dist/components/verbs/process-runner.d.ts.map +1 -1
- package/dist/components/verbs/r2-sync.d.ts.map +1 -1
- package/dist/components/verbs/run-agent.d.ts.map +1 -1
- package/dist/components/verbs/sbom-generator.d.ts.map +1 -1
- package/dist/components/verbs/sign.d.ts.map +1 -1
- package/dist/components/verbs/verify.d.ts.map +1 -1
- package/dist/components/verbs/vuln-gate.d.ts.map +1 -1
- package/dist/components/verbs/vuln-scan.d.ts.map +1 -1
- package/dist/components/verbs/wrangler.d.ts.map +1 -1
- package/dist/composite.d.ts.map +1 -1
- package/dist/config.d.ts +30 -7
- package/dist/config.d.ts.map +1 -1
- package/dist/declarable.d.ts +16 -0
- package/dist/declarable.d.ts.map +1 -1
- package/dist/discovery/cache.d.ts.map +1 -1
- package/dist/discovery/fold-import.d.ts +15 -5
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/param-deps.d.ts +16 -0
- package/dist/discovery/param-deps.d.ts.map +1 -1
- package/dist/discovery/sandbox/policy-wire.d.ts.map +1 -1
- package/dist/discovery/specifier-resolve.d.ts +37 -0
- package/dist/discovery/specifier-resolve.d.ts.map +1 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/fold/fold.d.ts +3 -1
- package/dist/fold/fold.d.ts.map +1 -1
- package/dist/graph-detail.d.ts.map +1 -1
- package/dist/graph-ir.d.ts.map +1 -1
- package/dist/graph-layout.d.ts.map +1 -1
- package/dist/import/base-parser.d.ts.map +1 -1
- package/dist/import/embedded.d.ts +7 -0
- package/dist/import/embedded.d.ts.map +1 -1
- package/dist/import/parser-options.d.ts +14 -0
- package/dist/import/parser-options.d.ts.map +1 -0
- package/dist/import/parser.d.ts +17 -0
- package/dist/import/parser.d.ts.map +1 -1
- package/dist/kubectl-context.d.ts.map +1 -1
- package/dist/lexicon-output.d.ts.map +1 -1
- package/dist/lexicon-schema.d.ts +3 -3
- package/dist/lexicon.d.ts +78 -3
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/assert-live.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.d.ts +15 -0
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/member-ledger.d.ts.map +1 -1
- package/dist/lifecycle/observe.d.ts.map +1 -1
- package/dist/lifecycle/plan-digest.d.ts +26 -5
- package/dist/lifecycle/plan-digest.d.ts.map +1 -1
- package/dist/lifecycle/plan-ledger.d.ts.map +1 -1
- package/dist/lifecycle/release-ledger.d.ts.map +1 -1
- package/dist/lifecycle/scenario.d.ts.map +1 -1
- package/dist/lifecycle/status.d.ts.map +1 -1
- package/dist/lifecycle/work-lease.d.ts +61 -2
- package/dist/lifecycle/work-lease.d.ts.map +1 -1
- package/dist/lint/config.d.ts +4 -4
- package/dist/lint/engine.d.ts +6 -0
- package/dist/lint/engine.d.ts.map +1 -1
- package/dist/lsp/lexicon-providers.d.ts.map +1 -1
- package/dist/op/activities/activity-contracts.d.ts +12 -11
- package/dist/op/activities/activity-contracts.d.ts.map +1 -1
- package/dist/op/activities/apply.d.ts +79 -9
- package/dist/op/activities/apply.d.ts.map +1 -1
- package/dist/op/activities/decide.d.ts.map +1 -1
- package/dist/op/activities/index.d.ts +1 -1
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activities/lexicon-upgrade.d.ts +44 -12
- package/dist/op/activities/lexicon-upgrade.d.ts.map +1 -1
- package/dist/op/activities/propose-upgrade.d.ts +2 -0
- package/dist/op/activities/propose-upgrade.d.ts.map +1 -1
- package/dist/op/activity-contract.d.ts +1 -1
- package/dist/op/activity-failure.d.ts.map +1 -1
- package/dist/op/activity-profiles.d.ts.map +1 -1
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/composites/apply-op.d.ts +12 -5
- package/dist/op/composites/apply-op.d.ts.map +1 -1
- package/dist/op/composites/lexicon-upgrade-op.d.ts +22 -6
- package/dist/op/composites/lexicon-upgrade-op.d.ts.map +1 -1
- package/dist/op/decide-backend.d.ts.map +1 -1
- package/dist/op/decide-read-inputs.d.ts.map +1 -1
- package/dist/op/emulator-lifecycle.d.ts +21 -2
- package/dist/op/emulator-lifecycle.d.ts.map +1 -1
- package/dist/op/gate-approval.d.ts +5 -1
- package/dist/op/gate-approval.d.ts.map +1 -1
- package/dist/op/gate-wait.d.ts.map +1 -1
- package/dist/op/gate.d.ts +19 -3
- package/dist/op/gate.d.ts.map +1 -1
- package/dist/op/index.d.ts +3 -1
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/local-executor.d.ts +6 -0
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/operator.d.ts.map +1 -1
- package/dist/op/run-context.d.ts +39 -0
- package/dist/op/run-context.d.ts.map +1 -0
- package/dist/op/run-live.d.ts.map +1 -1
- package/dist/op/runtimes/local.d.ts.map +1 -1
- package/dist/op/source-archive.d.ts.map +1 -1
- package/dist/op/steward-beside.d.ts.map +1 -1
- package/dist/op/steward-points.d.ts.map +1 -1
- package/dist/op/steward.d.ts.map +1 -1
- package/dist/op/types.d.ts +8 -3
- package/dist/op/types.d.ts.map +1 -1
- package/dist/op/work-lease-run.d.ts +24 -4
- package/dist/op/work-lease-run.d.ts.map +1 -1
- package/dist/project-root.d.ts.map +1 -1
- package/dist/provenance.d.ts +21 -0
- package/dist/provenance.d.ts.map +1 -1
- package/dist/pseudo-parameter.d.ts.map +1 -1
- package/dist/reconcile.d.ts.map +1 -1
- package/dist/secret-materialization.d.ts.map +1 -1
- package/dist/serializer.d.ts +8 -0
- package/dist/serializer.d.ts.map +1 -1
- package/dist/telemetry-attribution.d.ts +77 -0
- package/dist/telemetry-attribution.d.ts.map +1 -0
- package/dist/terraform/emit-source.d.ts.map +1 -1
- package/dist/terraform/parse.d.ts.map +1 -1
- package/dist/testing.d.ts.map +1 -1
- package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -1
- package/dist/workspace/__fixtures__/decide-workspace.d.ts +1 -1
- package/dist/workspace/__fixtures__/decide-workspace.d.ts.map +1 -1
- package/dist/workspace/agent-cli.d.ts +88 -0
- package/dist/workspace/agent-cli.d.ts.map +1 -0
- package/dist/workspace/box-factory.d.ts +73 -0
- package/dist/workspace/box-factory.d.ts.map +1 -0
- package/dist/workspace/changes-cli.d.ts.map +1 -1
- package/dist/workspace/changes.d.ts +8 -1
- package/dist/workspace/changes.d.ts.map +1 -1
- package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts.map +1 -1
- package/dist/workspace/checks/boxes.d.ts.map +1 -1
- package/dist/workspace/checks/links.d.ts +1 -0
- package/dist/workspace/checks/links.d.ts.map +1 -1
- package/dist/workspace/checks/live.d.ts +40 -0
- package/dist/workspace/checks/live.d.ts.map +1 -0
- package/dist/workspace/checks.d.ts +26 -2
- package/dist/workspace/checks.d.ts.map +1 -1
- package/dist/workspace/compose-graph.d.ts +73 -0
- package/dist/workspace/compose-graph.d.ts.map +1 -1
- package/dist/workspace/conformance/index.d.ts +67 -6
- package/dist/workspace/conformance/index.d.ts.map +1 -1
- package/dist/workspace/conformance/vitest.d.ts +12 -3
- package/dist/workspace/conformance/vitest.d.ts.map +1 -1
- package/dist/workspace/conformance/writer.d.ts +368 -0
- package/dist/workspace/conformance/writer.d.ts.map +1 -0
- package/dist/workspace/decide.d.ts +1 -1
- package/dist/workspace/decide.d.ts.map +1 -1
- package/dist/workspace/declaration.d.ts +119 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.schema.json +368 -4
- package/dist/workspace/export-cli.d.ts +12 -0
- package/dist/workspace/export-cli.d.ts.map +1 -0
- package/dist/workspace/export.d.ts +145 -0
- package/dist/workspace/export.d.ts.map +1 -0
- package/dist/workspace/generated-files.d.ts.map +1 -1
- package/dist/workspace/graph-cache.d.ts.map +1 -1
- package/dist/workspace/graph-cli.d.ts +2 -0
- package/dist/workspace/graph-cli.d.ts.map +1 -1
- package/dist/workspace/identity.d.ts +111 -0
- package/dist/workspace/identity.d.ts.map +1 -0
- package/dist/workspace/import.d.ts +73 -0
- package/dist/workspace/import.d.ts.map +1 -0
- package/dist/workspace/intent-cli.d.ts +7 -4
- package/dist/workspace/intent-cli.d.ts.map +1 -1
- package/dist/workspace/intent-record.d.ts +8 -1
- package/dist/workspace/intent-record.d.ts.map +1 -1
- package/dist/workspace/intent.d.ts +149 -5
- package/dist/workspace/intent.d.ts.map +1 -1
- package/dist/workspace/kinds.d.ts +24 -6
- package/dist/workspace/kinds.d.ts.map +1 -1
- package/dist/workspace/lineage-adopt-cli.d.ts +15 -0
- package/dist/workspace/lineage-adopt-cli.d.ts.map +1 -0
- package/dist/workspace/lineage-adopt.d.ts +106 -0
- package/dist/workspace/lineage-adopt.d.ts.map +1 -0
- package/dist/workspace/lineage-check.d.ts +9 -2
- package/dist/workspace/lineage-check.d.ts.map +1 -1
- package/dist/workspace/lineage-cli.d.ts +6 -1
- package/dist/workspace/lineage-cli.d.ts.map +1 -1
- package/dist/workspace/lineage-hash-index.d.ts +110 -0
- package/dist/workspace/lineage-hash-index.d.ts.map +1 -0
- package/dist/workspace/lineage-init.d.ts +10 -0
- package/dist/workspace/lineage-init.d.ts.map +1 -1
- package/dist/workspace/lineage-lock.d.ts +149 -2
- package/dist/workspace/lineage-lock.d.ts.map +1 -1
- package/dist/workspace/lineage-migrations.d.ts +15 -3
- package/dist/workspace/lineage-migrations.d.ts.map +1 -1
- package/dist/workspace/lineage-provenance.d.ts +18 -0
- package/dist/workspace/lineage-provenance.d.ts.map +1 -0
- package/dist/workspace/lineage-upgrade-cli.d.ts +2 -0
- package/dist/workspace/lineage-upgrade-cli.d.ts.map +1 -1
- package/dist/workspace/lineage-upgrade.d.ts +30 -1
- package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
- package/dist/workspace/lineage-versions.d.ts +116 -0
- package/dist/workspace/lineage-versions.d.ts.map +1 -0
- package/dist/workspace/links.d.ts +31 -5
- package/dist/workspace/links.d.ts.map +1 -1
- package/dist/workspace/ls-generated.d.ts +37 -0
- package/dist/workspace/ls-generated.d.ts.map +1 -0
- package/dist/workspace/ls.d.ts +4 -0
- package/dist/workspace/ls.d.ts.map +1 -1
- package/dist/workspace/member-commands.d.ts.map +1 -1
- package/dist/workspace/nested-graph.d.ts +56 -0
- package/dist/workspace/nested-graph.d.ts.map +1 -0
- package/dist/workspace/nesting.d.ts +21 -0
- package/dist/workspace/nesting.d.ts.map +1 -0
- package/dist/workspace/pin-cli.d.ts +10 -0
- package/dist/workspace/pin-cli.d.ts.map +1 -0
- package/dist/workspace/pin-integrity.d.ts +51 -0
- package/dist/workspace/pin-integrity.d.ts.map +1 -0
- package/dist/workspace/points.d.ts.map +1 -1
- package/dist/workspace/principal-classes.d.ts +114 -0
- package/dist/workspace/principal-classes.d.ts.map +1 -0
- package/dist/workspace/reason-codes.d.ts +47 -3
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/record-assets.d.ts.map +1 -1
- package/dist/workspace/record-sessions.d.ts +7 -11
- package/dist/workspace/record-sessions.d.ts.map +1 -1
- package/dist/workspace/records-checkout.d.ts +54 -0
- package/dist/workspace/records-checkout.d.ts.map +1 -0
- package/dist/workspace/records-cli.d.ts +57 -2
- package/dist/workspace/records-cli.d.ts.map +1 -1
- package/dist/workspace/records-close.d.ts +5 -2
- package/dist/workspace/records-close.d.ts.map +1 -1
- package/dist/workspace/records-since.d.ts.map +1 -1
- package/dist/workspace/records-write.d.ts +23 -4
- package/dist/workspace/records-write.d.ts.map +1 -1
- package/dist/workspace/records.d.ts +58 -5
- package/dist/workspace/records.d.ts.map +1 -1
- package/dist/workspace/returns.d.ts +129 -0
- package/dist/workspace/returns.d.ts.map +1 -0
- package/dist/workspace/runs-cli.d.ts +164 -0
- package/dist/workspace/runs-cli.d.ts.map +1 -0
- package/dist/workspace/runs.d.ts +423 -0
- package/dist/workspace/runs.d.ts.map +1 -0
- package/dist/workspace/source-block.d.ts +1 -1
- package/dist/workspace/source-block.d.ts.map +1 -1
- package/dist/workspace/status-gates.d.ts +15 -3
- package/dist/workspace/status-gates.d.ts.map +1 -1
- package/dist/workspace/status.d.ts +10 -0
- package/dist/workspace/status.d.ts.map +1 -1
- package/dist/workspace/symbols.d.ts +52 -0
- package/dist/workspace/symbols.d.ts.map +1 -0
- package/dist/workspace/template-manifest.d.ts +11 -3
- package/dist/workspace/template-manifest.d.ts.map +1 -1
- package/dist/workspace/trailer-joins.d.ts +46 -0
- package/dist/workspace/trailer-joins.d.ts.map +1 -0
- package/dist/workspace/trailers.d.ts +89 -0
- package/dist/workspace/trailers.d.ts.map +1 -0
- package/dist/workspace/trust/attestor.d.ts +8 -0
- package/dist/workspace/trust/attestor.d.ts.map +1 -1
- package/dist/workspace/trust/dsse.d.ts +58 -0
- package/dist/workspace/trust/dsse.d.ts.map +1 -0
- package/dist/workspace/trust/evidence-cli.d.ts +66 -0
- package/dist/workspace/trust/evidence-cli.d.ts.map +1 -0
- package/dist/workspace/trust/evidence.d.ts +93 -0
- package/dist/workspace/trust/evidence.d.ts.map +1 -0
- package/dist/workspace/trust/policy.d.ts +54 -1
- package/dist/workspace/trust/policy.d.ts.map +1 -1
- package/dist/workspace/trust/provenance.d.ts +21 -1
- package/dist/workspace/trust/provenance.d.ts.map +1 -1
- package/dist/workspace/trust/rotation.d.ts +132 -0
- package/dist/workspace/trust/rotation.d.ts.map +1 -0
- package/dist/workspace/trust/seal.d.ts +33 -0
- package/dist/workspace/trust/seal.d.ts.map +1 -1
- package/dist/workspace/trust/signers-cli.d.ts +49 -0
- package/dist/workspace/trust/signers-cli.d.ts.map +1 -0
- package/dist/workspace/trust/ssh-commit.d.ts +12 -0
- package/dist/workspace/trust/ssh-commit.d.ts.map +1 -1
- package/dist/workspace/trust/test-repo.d.ts +13 -0
- package/dist/workspace/trust/test-repo.d.ts.map +1 -1
- package/dist/workspace/trust/verify.d.ts +15 -0
- package/dist/workspace/trust/verify.d.ts.map +1 -1
- package/dist/workspace/work-cli.d.ts +39 -3
- package/dist/workspace/work-cli.d.ts.map +1 -1
- package/dist/workspace/work-evidence.d.ts +1 -1
- package/dist/workspace/work-evidence.d.ts.map +1 -1
- package/dist/workspace/work.d.ts +26 -1
- package/dist/workspace/work.d.ts.map +1 -1
- package/dist/workspace/workspace-principals.schema.json +60 -0
- package/dist/workspace/write-scope.d.ts +259 -0
- package/dist/workspace/write-scope.d.ts.map +1 -0
- package/dist/yaml.d.ts +21 -39
- package/dist/yaml.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/audit/prior-art.ts +15 -0
- package/src/build.ts +8 -0
- package/src/cli/build-options.ts +4 -1
- package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +1 -1
- package/src/cli/commands/build.ts +2 -0
- package/src/cli/commands/carve-emit-state.test.ts +4 -0
- package/src/cli/commands/carve-emit.ts +5 -19
- package/src/cli/commands/check-lexicon.test.ts +62 -1
- package/src/cli/commands/check-lexicon.ts +36 -6
- package/src/cli/commands/import-live.test.ts +87 -1
- package/src/cli/commands/import-parser-options.test.ts +133 -0
- package/src/cli/commands/import.ts +69 -25
- package/src/cli/commands/init-lexicon/templates/project.ts +1 -1
- package/src/cli/commands/init.test.ts +8 -1
- package/src/cli/commands/init.ts +23 -8
- package/src/cli/commands/lint.test.ts +37 -0
- package/src/cli/commands/onboard.test.ts +53 -3
- package/src/cli/commands/onboard.ts +52 -4
- package/src/cli/commands/pinned-upgrade.ts +73 -33
- package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +2 -2
- package/src/cli/handlers/components.ts +2 -2
- package/src/cli/handlers/dev.ts +8 -6
- package/src/cli/handlers/fan-out.test.ts +6 -6
- package/src/cli/handlers/fan-out.ts +2 -1
- package/src/cli/handlers/graph.test.ts +42 -0
- package/src/cli/handlers/graph.ts +22 -0
- package/src/cli/handlers/lifecycle.test.ts +67 -0
- package/src/cli/handlers/lifecycle.ts +6 -5
- package/src/cli/handlers/misc.ts +2 -0
- package/src/cli/handlers/operator.ts +88 -4
- package/src/cli/handlers/run.test.ts +111 -0
- package/src/cli/handlers/run.ts +85 -24
- package/src/cli/handlers/scenario.ts +2 -2
- package/src/cli/main.test.ts +33 -0
- package/src/cli/main.ts +168 -17
- package/src/cli/mcp/workspace-tools.test.ts +5 -2
- package/src/cli/mcp/workspace-tools.ts +62 -4
- package/src/cli/registry.ts +33 -2
- package/src/cli/serve-mcp-workspace.test.ts +1 -1
- package/src/codegen/pinned-upgrade-multi.test.ts +157 -0
- package/src/codegen/pinned-upgrade-tags.test.ts +112 -0
- package/src/codegen/pinned-upgrade.test.ts +104 -0
- package/src/codegen/pinned-upgrade.ts +250 -56
- package/src/codegen/release-wiring.test.ts +5 -1
- package/src/codegen/testdata/postgres-tags.json +242 -0
- package/src/components/fan-out-output.test.ts +1 -1
- package/src/components/fan-out.test.ts +1 -1
- package/src/components/promote.test.ts +1 -1
- package/src/config.test.ts +21 -0
- package/src/config.ts +27 -0
- package/src/content-digest.test.ts +2 -2
- package/src/declarable.test.ts +21 -0
- package/src/declarable.ts +19 -0
- package/src/discovery/fold-composite.test.ts +41 -5
- package/src/discovery/fold-import.test.ts +129 -0
- package/src/discovery/fold-import.ts +643 -131
- package/src/discovery/fold-package-composite.test.ts +264 -0
- package/src/discovery/fold-specifiers.test.ts +191 -0
- package/src/discovery/param-deps.test.ts +66 -1
- package/src/discovery/param-deps.ts +45 -0
- package/src/discovery/specifier-resolve.ts +344 -0
- package/src/fold/fold.test.ts +39 -1
- package/src/fold/fold.ts +83 -9
- package/src/fold-provenance.test.ts +140 -0
- package/src/graph-ir.test.ts +83 -0
- package/src/graph-ir.ts +29 -0
- package/src/import/embedded.test.ts +20 -0
- package/src/import/embedded.ts +13 -0
- package/src/import/parser-options.ts +46 -0
- package/src/import/parser.ts +20 -0
- package/src/lexicon.ts +79 -3
- package/src/lifecycle/gate-ledger.test.ts +14 -0
- package/src/lifecycle/gate-ledger.ts +18 -1
- package/src/lifecycle/observe.test.ts +18 -0
- package/src/lifecycle/observe.ts +6 -4
- package/src/lifecycle/plan-digest.test.ts +54 -3
- package/src/lifecycle/plan-digest.ts +38 -8
- package/src/lifecycle/snapshot.ts +2 -2
- package/src/lifecycle/work-lease.test.ts +1 -1
- package/src/lifecycle/work-lease.ts +93 -2
- package/src/lint/engine.test.ts +55 -0
- package/src/lint/engine.ts +30 -4
- package/src/lint/output-docs.test.ts +59 -56
- package/src/op/activities/activity-contracts.ts +1 -0
- package/src/op/activities/apply.test.ts +189 -5
- package/src/op/activities/apply.ts +230 -8
- package/src/op/activities/index.ts +3 -0
- package/src/op/activities/lexicon-upgrade.test.ts +160 -6
- package/src/op/activities/lexicon-upgrade.ts +154 -51
- package/src/op/activities/propose-upgrade.ts +8 -5
- package/src/op/composites/apply-op.ts +12 -5
- package/src/op/composites/composites.test.ts +30 -3
- package/src/op/composites/lexicon-upgrade-op.test.ts +7 -2
- package/src/op/composites/lexicon-upgrade-op.ts +29 -8
- package/src/op/emulator-lifecycle.test.ts +23 -0
- package/src/op/emulator-lifecycle.ts +52 -14
- package/src/op/gate-approval.test.ts +91 -0
- package/src/op/gate-approval.ts +5 -1
- package/src/op/gate.ts +34 -8
- package/src/op/index.ts +3 -1
- package/src/op/local-executor.test.ts +15 -0
- package/src/op/local-executor.ts +40 -12
- package/src/op/local-output.ts +1 -1
- package/src/op/operator.ts +4 -0
- package/src/op/run-context.test.ts +141 -0
- package/src/op/run-context.ts +51 -0
- package/src/op/runtimes/local.test.ts +14 -0
- package/src/op/runtimes/local.ts +2 -0
- package/src/op/steward-beside.ts +2 -0
- package/src/op/types.ts +8 -3
- package/src/op/work-lease-run.test.ts +47 -0
- package/src/op/work-lease-run.ts +73 -11
- package/src/provenance.ts +35 -0
- package/src/serializer.ts +9 -0
- package/src/telemetry-attribution.test.ts +91 -0
- package/src/telemetry-attribution.ts +145 -0
- package/src/workspace/__fixtures__/studio-template-84c8b21.json +1 -1
- package/src/workspace/agent-cli.ts +138 -0
- package/src/workspace/agent.schema.json +378 -0
- package/src/workspace/behold-kinds.test.ts +1 -1
- package/src/workspace/box-factory.test.ts +320 -0
- package/src/workspace/box-factory.ts +103 -0
- package/src/workspace/changes-cli.ts +5 -0
- package/src/workspace/changes.schema.json +171 -1
- package/src/workspace/changes.ts +61 -4
- package/src/workspace/check-live.test.ts +192 -0
- package/src/workspace/check.schema.json +64 -0
- package/src/workspace/checks/boxes.test.ts +2 -0
- package/src/workspace/checks/links.ts +23 -2
- package/src/workspace/checks/live.ts +113 -0
- package/src/workspace/checks.test.ts +2 -2
- package/src/workspace/checks.ts +49 -8
- package/src/workspace/compose-graph.test.ts +47 -0
- package/src/workspace/compose-graph.ts +127 -3
- package/src/workspace/conformance/__fixture__/decisions/decision.kind.mjs +7 -1
- package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +5 -0
- package/src/workspace/conformance/__writer_fixture__/answers/answer.kind.mjs +27 -0
- package/src/workspace/conformance/__writer_fixture__/answers/answer.schema.json +95 -0
- package/src/workspace/conformance/__writer_fixture__/decisions/points.json +132 -0
- package/src/workspace/conformance/__writer_fixture__/sessions/session.kind.mjs +48 -0
- package/src/workspace/conformance/__writer_fixture__/sessions/session.schema.json +108 -0
- package/src/workspace/conformance/__writer_fixture__/work/W-001-the-writer-suite-works-this-item.md +25 -0
- package/src/workspace/conformance/__writer_fixture__/work/work.kind.mjs +60 -0
- package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +578 -0
- package/src/workspace/conformance/conformance.e2e.test.ts +35 -3
- package/src/workspace/conformance/index.mjs +30 -3
- package/src/workspace/conformance/index.ts +103 -15
- package/src/workspace/conformance/vitest.ts +44 -2
- package/src/workspace/conformance/writer-conformance.e2e.test.ts +131 -0
- package/src/workspace/conformance/writer.test.ts +156 -0
- package/src/workspace/conformance/writer.ts +880 -0
- package/src/workspace/decide.ts +7 -1
- package/src/workspace/declaration.schema.json +368 -4
- package/src/workspace/declaration.ts +270 -1
- package/src/workspace/declared-kinds.test.ts +33 -0
- package/src/workspace/evidence.schema.json +279 -0
- package/src/workspace/export-cli.ts +180 -0
- package/src/workspace/export-import.test.ts +282 -0
- package/src/workspace/export.ts +486 -0
- package/src/workspace/graph-cli.ts +51 -2
- package/src/workspace/graph-contract.test.ts +89 -4
- package/src/workspace/graph.schema.json +453 -1
- package/src/workspace/identity.test.ts +196 -0
- package/src/workspace/identity.ts +186 -0
- package/src/workspace/import.ts +325 -0
- package/src/workspace/intent-cli.ts +64 -10
- package/src/workspace/intent-record.schema.json +208 -0
- package/src/workspace/intent-record.ts +35 -1
- package/src/workspace/intent-why.test.ts +274 -0
- package/src/workspace/intent.schema.json +718 -7
- package/src/workspace/intent.test.ts +11 -2
- package/src/workspace/intent.ts +495 -32
- package/src/workspace/kinds.test.ts +5 -5
- package/src/workspace/kinds.ts +62 -33
- package/src/workspace/lineage-adopt-cli.ts +103 -0
- package/src/workspace/lineage-adopt.test.ts +552 -0
- package/src/workspace/lineage-adopt.ts +452 -0
- package/src/workspace/lineage-check.ts +26 -5
- package/src/workspace/lineage-cli.ts +12 -2
- package/src/workspace/lineage-hash-index.ts +305 -0
- package/src/workspace/lineage-init.test.ts +9 -0
- package/src/workspace/lineage-init.ts +29 -9
- package/src/workspace/lineage-lock.ts +54 -0
- package/src/workspace/lineage-migrations.test.ts +27 -0
- package/src/workspace/lineage-migrations.ts +44 -14
- package/src/workspace/lineage-provenance.ts +40 -0
- package/src/workspace/lineage-upgrade-cli.ts +9 -5
- package/src/workspace/lineage-upgrade.test.ts +92 -1
- package/src/workspace/lineage-upgrade.ts +111 -16
- package/src/workspace/lineage-versions.ts +348 -0
- package/src/workspace/links.test.ts +121 -2
- package/src/workspace/links.ts +97 -6
- package/src/workspace/ls-contract.test.ts +84 -1
- package/src/workspace/ls-generated.ts +111 -0
- package/src/workspace/ls.schema.json +19 -0
- package/src/workspace/ls.ts +8 -1
- package/src/workspace/member-commands.ts +3 -1
- package/src/workspace/nested-graph.test.ts +176 -0
- package/src/workspace/nested-graph.ts +169 -0
- package/src/workspace/nesting.ts +37 -0
- package/src/workspace/pin-cli.test.ts +71 -0
- package/src/workspace/pin-cli.ts +57 -0
- package/src/workspace/pin-integrity.test.ts +121 -0
- package/src/workspace/pin-integrity.ts +104 -0
- package/src/workspace/points-write.schema.json +2 -0
- package/src/workspace/principal-classes.test.ts +175 -0
- package/src/workspace/principal-classes.ts +307 -0
- package/src/workspace/read-contract.test.ts +24 -0
- package/src/workspace/reason-codes.test.ts +20 -2
- package/src/workspace/reason-codes.ts +53 -3
- package/src/workspace/record-sessions.ts +12 -14
- package/src/workspace/records-amend.schema.json +6 -0
- package/src/workspace/records-checkout.ts +106 -0
- package/src/workspace/records-cli.ts +195 -22
- package/src/workspace/records-close.schema.json +7 -2
- package/src/workspace/records-close.ts +10 -2
- package/src/workspace/records-formats.test.ts +13 -5
- package/src/workspace/records-new.schema.json +6 -0
- package/src/workspace/records-review.schema.json +6 -0
- package/src/workspace/records-sessions.test.ts +23 -6
- package/src/workspace/records-uncommitted.test.ts +171 -0
- package/src/workspace/records-write.test.ts +65 -8
- package/src/workspace/records-write.ts +92 -6
- package/src/workspace/records.schema.json +127 -4
- package/src/workspace/records.ts +123 -5
- package/src/workspace/returns.ts +328 -0
- package/src/workspace/runs-cli.ts +381 -0
- package/src/workspace/runs-write.schema.json +583 -0
- package/src/workspace/runs.schema.json +810 -0
- package/src/workspace/runs.test.ts +242 -0
- package/src/workspace/runs.ts +709 -0
- package/src/workspace/signers.schema.json +206 -0
- package/src/workspace/status-contract.test.ts +5 -0
- package/src/workspace/status-gates.ts +24 -9
- package/src/workspace/status.schema.json +127 -2
- package/src/workspace/status.ts +21 -1
- package/src/workspace/symbols.ts +180 -0
- package/src/workspace/template-manifest.ts +22 -4
- package/src/workspace/trailer-joins.test.ts +171 -0
- package/src/workspace/trailer-joins.ts +82 -0
- package/src/workspace/trailers.ts +148 -0
- package/src/workspace/trust/attestor.ts +15 -0
- package/src/workspace/trust/dsse.ts +134 -0
- package/src/workspace/trust/evidence-cli.ts +195 -0
- package/src/workspace/trust/evidence.test.ts +241 -0
- package/src/workspace/trust/evidence.ts +207 -0
- package/src/workspace/trust/policy.ts +110 -3
- package/src/workspace/trust/provenance.ts +41 -4
- package/src/workspace/trust/record-seal.test.ts +1 -1
- package/src/workspace/trust/rotation.test.ts +258 -0
- package/src/workspace/trust/rotation.ts +336 -0
- package/src/workspace/trust/seal.ts +63 -1
- package/src/workspace/trust/signers-cli.ts +178 -0
- package/src/workspace/trust/ssh-commit.ts +53 -4
- package/src/workspace/trust/test-repo.ts +18 -0
- package/src/workspace/trust/trust.test.ts +8 -2
- package/src/workspace/trust/verify-cli.ts +1 -0
- package/src/workspace/trust/verify.ts +22 -0
- package/src/workspace/work-cli.e2e.test.ts +28 -1
- package/src/workspace/work-cli.ts +133 -8
- package/src/workspace/work-evidence.schema.json +6 -0
- package/src/workspace/work-history.schema.json +73 -1
- package/src/workspace/work-history.test.ts +48 -5
- package/src/workspace/work-kind-fields.test.ts +154 -0
- package/src/workspace/work-lease.schema.json +14 -1
- package/src/workspace/work.ts +82 -0
- package/src/workspace/workspace-principals.schema.json +60 -0
- package/src/workspace/write-scope.test.ts +466 -0
- package/src/workspace/write-scope.ts +573 -0
- package/src/yaml.test.ts +111 -1
- package/src/yaml.ts +141 -549
package/src/op/local-executor.ts
CHANGED
|
@@ -29,6 +29,7 @@ import { stepTimeoutProblem } from "./activity-profiles";
|
|
|
29
29
|
import { describeGateMismatch, evaluateGate, gitGateLedgerPort, type GateCheck, type GateLedgerPort } from "./gate";
|
|
30
30
|
import { gateName } from "./gate-name";
|
|
31
31
|
import type { ResolvedGateApproval } from "./gate-approval";
|
|
32
|
+
import { withOpRunContext, type OpRunContext, type PassedGate } from "./run-context";
|
|
32
33
|
import type { PendingGateRecord } from "../lifecycle/gate-ledger";
|
|
33
34
|
import { isPointWait, type WaitingPoint } from "./steward-points";
|
|
34
35
|
import { isGateWait } from "./gate-wait";
|
|
@@ -416,9 +417,11 @@ async function runStep(
|
|
|
416
417
|
if (step.id) resultsById.set(step.id, result);
|
|
417
418
|
const record: StepRecord = { ...base, status: "ok", durationMs: Date.now() - start };
|
|
418
419
|
const attrs = outcomeAttributesOf(step);
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
420
|
+
// An attribute whose path the result does not hold is left off, rather than recorded as undefined.
|
|
421
|
+
const outcomes = attrs.map((a) => ({ name: a.name, value: resolvePath(result, a.from) })).filter((o) => o.value !== undefined);
|
|
422
|
+
if (outcomes.length > 0) {
|
|
423
|
+
record.outcomes = outcomes;
|
|
424
|
+
record.outcome = outcomes[0];
|
|
422
425
|
}
|
|
423
426
|
return { record, result };
|
|
424
427
|
} catch (err) {
|
|
@@ -482,6 +485,8 @@ interface GateContext {
|
|
|
482
485
|
runId?: string;
|
|
483
486
|
/** Called once per settled step, in production order (#2121) — what `--progress-json` streams from. */
|
|
484
487
|
onRecord?: (record: StepRecord) => void;
|
|
488
|
+
/** The run context's list of passed gates (#2522). A gate that passes is appended here. */
|
|
489
|
+
passed?: PassedGate[];
|
|
485
490
|
/** The run's hold on its work item's lease (#2748). Absent in compensation phases, which run whatever the lease did. */
|
|
486
491
|
work?: RunWorkLease;
|
|
487
492
|
}
|
|
@@ -643,6 +648,17 @@ async function runGateStep(
|
|
|
643
648
|
|
|
644
649
|
if (check.satisfied) {
|
|
645
650
|
const { resolution } = check;
|
|
651
|
+
const passedApproval: NonNullable<StepRecord["approval"]> = {
|
|
652
|
+
gate: gateName(step),
|
|
653
|
+
resolvedBy: resolution.resolvedBy,
|
|
654
|
+
timestamp: resolution.timestamp,
|
|
655
|
+
...(resolution.url ? { url: resolution.url } : {}),
|
|
656
|
+
...(check.via ? { via: check.via } : {}),
|
|
657
|
+
...(check.approvals ? { approvers: check.approvals.map((r) => r.resolvedBy) } : {}),
|
|
658
|
+
};
|
|
659
|
+
// #2522: a later step reads this gate's approval from the run context
|
|
660
|
+
// rather than re-tallying the ledger, so it sees the decision this run made.
|
|
661
|
+
gates.passed?.push({ gate: gateName(step), approval: passedApproval });
|
|
646
662
|
return {
|
|
647
663
|
record: {
|
|
648
664
|
phase: phaseName,
|
|
@@ -650,14 +666,7 @@ async function runGateStep(
|
|
|
650
666
|
args: {},
|
|
651
667
|
status: "ok",
|
|
652
668
|
durationMs: Date.now() - start,
|
|
653
|
-
approval:
|
|
654
|
-
gate: gateName(step),
|
|
655
|
-
resolvedBy: resolution.resolvedBy,
|
|
656
|
-
timestamp: resolution.timestamp,
|
|
657
|
-
...(resolution.url ? { url: resolution.url } : {}),
|
|
658
|
-
...(check.via ? { via: check.via } : {}),
|
|
659
|
-
...(check.approvals ? { approvers: check.approvals.map((r) => r.resolvedBy) } : {}),
|
|
660
|
-
},
|
|
669
|
+
approval: passedApproval,
|
|
661
670
|
},
|
|
662
671
|
};
|
|
663
672
|
}
|
|
@@ -971,6 +980,12 @@ export interface RunOpOptions {
|
|
|
971
980
|
now?: string;
|
|
972
981
|
/** Identifies this run on any pending fact it records. */
|
|
973
982
|
runId?: string;
|
|
983
|
+
/**
|
|
984
|
+
* The environment the run was started for (`--env`, #2522). The executor
|
|
985
|
+
* only hands it to activities through the run context (`./run-context.ts`).
|
|
986
|
+
* The run ledger path still comes from the Op's `labels.Env`.
|
|
987
|
+
*/
|
|
988
|
+
env?: string;
|
|
974
989
|
/**
|
|
975
990
|
* Called once per settled step, in the order the records are produced
|
|
976
991
|
* (#2121) — what the local op runtime (./runtimes/local.ts) feeds
|
|
@@ -1039,6 +1054,13 @@ export async function runOpLocally(
|
|
|
1039
1054
|
const turn = currentStewardTurn();
|
|
1040
1055
|
const steward = options.steward ?? turn?.steward;
|
|
1041
1056
|
const restore = turn && turn.run !== runId ? enterStewardTurn({ ...turn, run: runId }) : undefined;
|
|
1057
|
+
// #2522: every activity in this run can read the run it is in.
|
|
1058
|
+
const run: OpRunContext = {
|
|
1059
|
+
op: config.name,
|
|
1060
|
+
runId,
|
|
1061
|
+
...(options.env !== undefined ? { env: options.env } : {}),
|
|
1062
|
+
passedGates: [],
|
|
1063
|
+
};
|
|
1042
1064
|
// A run that writes to the ledger also says what it is doing while it runs
|
|
1043
1065
|
// (./run-live.ts), and removes that once its record is written.
|
|
1044
1066
|
const live = options.ledger
|
|
@@ -1052,7 +1074,11 @@ export async function runOpLocally(
|
|
|
1052
1074
|
})
|
|
1053
1075
|
: undefined;
|
|
1054
1076
|
try {
|
|
1055
|
-
return await withLiveRun(live, () =>
|
|
1077
|
+
return await withLiveRun(live, () =>
|
|
1078
|
+
withOpRunContext(run, () =>
|
|
1079
|
+
runOpInTurn(config, activities, profiles, signal, { ...options, runId, ...(steward ? { steward } : {}) }, run),
|
|
1080
|
+
),
|
|
1081
|
+
);
|
|
1056
1082
|
} finally {
|
|
1057
1083
|
live?.close();
|
|
1058
1084
|
restore?.();
|
|
@@ -1065,6 +1091,7 @@ async function runOpInTurn(
|
|
|
1065
1091
|
profiles: Record<string, ActivityProfile>,
|
|
1066
1092
|
signal: AbortSignal | undefined,
|
|
1067
1093
|
options: RunOpOptions & { runId: string },
|
|
1094
|
+
run: OpRunContext,
|
|
1068
1095
|
): Promise<OpRunResult> {
|
|
1069
1096
|
const runId = options.runId;
|
|
1070
1097
|
|
|
@@ -1074,6 +1101,7 @@ async function runOpInTurn(
|
|
|
1074
1101
|
...(options.now ? { now: options.now } : {}),
|
|
1075
1102
|
runId,
|
|
1076
1103
|
...(options.onRecord ? { onRecord: options.onRecord } : {}),
|
|
1104
|
+
passed: run.passedGates,
|
|
1077
1105
|
};
|
|
1078
1106
|
|
|
1079
1107
|
// Effect steps are ordered (read-compare-run-write): refuse them in a
|
package/src/op/local-output.ts
CHANGED
|
@@ -66,7 +66,7 @@ export function renderHuman(result: OpRunResult, write: Writer = stderr): void {
|
|
|
66
66
|
: work.released
|
|
67
67
|
? `released ${work.outcome}`
|
|
68
68
|
: "not released; it runs out on its own";
|
|
69
|
-
write(`[work] ${work.item} held by ${work.holder}${work.branch ? ` on ${work.branch}` : ""}: ${end}`);
|
|
69
|
+
write(`[work] ${work.item} held by ${work.holder}${work.branch ? ` on ${work.branch}` : ""}: ${end}${work.kept ? `; the attempt is kept at ${work.kept}` : ""}`);
|
|
70
70
|
} else if (work?.refusal) {
|
|
71
71
|
write(`[work] nothing claimed: ${work.refusal}`);
|
|
72
72
|
}
|
package/src/op/operator.ts
CHANGED
|
@@ -577,6 +577,10 @@ export async function runOperatorRound(opts: OperatorRoundOptions): Promise<Oper
|
|
|
577
577
|
try {
|
|
578
578
|
const result = await runOpLocally(config, opts.activities, opts.profiles, opts.signal, {
|
|
579
579
|
runId,
|
|
580
|
+
// #2522: the environment the operator was started for (`--env`, or a
|
|
581
|
+
// steward's environment) reaches the activities through the run
|
|
582
|
+
// context, as `chant run --env` does.
|
|
583
|
+
...(opts.env ?? opts.stewardEnv ? { env: opts.env ?? opts.stewardEnv } : {}),
|
|
580
584
|
...(steward ? { steward: steward.name } : {}),
|
|
581
585
|
ledger: { cwd: opts.cwd },
|
|
582
586
|
// A steward's turn claims work leases as `<steward>/<op>@<holder>`
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The run context an activity reads (#2522): the Op, the run id the run
|
|
3
|
+
* ledger records, `--env`, and the gates the run has passed.
|
|
4
|
+
*/
|
|
5
|
+
import { describe, test, expect } from "vitest";
|
|
6
|
+
import type { OpConfig } from "./types";
|
|
7
|
+
import type { ActivityFn, ActivityProfile } from "./activity-registry";
|
|
8
|
+
import { runOpLocally } from "./local-executor";
|
|
9
|
+
import { memoryGateLedgerPort } from "./gate";
|
|
10
|
+
import { currentOpRun, type OpRunContext } from "./run-context";
|
|
11
|
+
import { gatePolicyVersion, type GatePolicyRef, type ResolvedGateApproval } from "./gate-approval";
|
|
12
|
+
import type { GateResolutionRecord } from "../lifecycle/gate-ledger";
|
|
13
|
+
|
|
14
|
+
const PROFILES: Record<string, ActivityProfile> = {
|
|
15
|
+
fastIdempotent: { timeout: "5m", retry: { maximumAttempts: 1 } },
|
|
16
|
+
};
|
|
17
|
+
const NOW = "2026-09-05T12:00:00.000Z";
|
|
18
|
+
|
|
19
|
+
function op(steps: OpConfig["phases"][number]["steps"]): OpConfig {
|
|
20
|
+
return { name: "release", overview: "", phases: [{ name: "main", steps }] };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("the Op run context (#2522)", () => {
|
|
24
|
+
test("an activity reads the Op, the run id and --env", async () => {
|
|
25
|
+
let seen: OpRunContext | undefined;
|
|
26
|
+
const activities = new Map<string, ActivityFn>([
|
|
27
|
+
["deploy", async () => { await Promise.resolve(); seen = currentOpRun(); }],
|
|
28
|
+
]);
|
|
29
|
+
const result = await runOpLocally(op([{ kind: "activity", fn: "deploy" }]), activities, PROFILES, undefined, {
|
|
30
|
+
runId: "local-123",
|
|
31
|
+
env: "staging",
|
|
32
|
+
});
|
|
33
|
+
expect(seen).toEqual({ op: "release", runId: "local-123", env: "staging", passedGates: [] });
|
|
34
|
+
expect(result.record?.id).toBe("local-123");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("without --env the context carries none, and outside a run there is no context", async () => {
|
|
38
|
+
let seen: OpRunContext | undefined;
|
|
39
|
+
const activities = new Map<string, ActivityFn>([["deploy", async () => { seen = currentOpRun(); }]]);
|
|
40
|
+
await runOpLocally(op([{ kind: "activity", fn: "deploy" }]), activities, PROFILES, undefined, { runId: "r1" });
|
|
41
|
+
expect(seen).toEqual({ op: "release", runId: "r1", passedGates: [] });
|
|
42
|
+
expect(currentOpRun()).toBeUndefined();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("a step after a passed gate reads the gate's approval", async () => {
|
|
46
|
+
const seen: OpRunContext[] = [];
|
|
47
|
+
const activities = new Map<string, ActivityFn>([
|
|
48
|
+
["plan", async () => { seen.push(currentOpRun()!); }],
|
|
49
|
+
["apply", async () => { seen.push(currentOpRun()!); }],
|
|
50
|
+
]);
|
|
51
|
+
const port = memoryGateLedgerPort({
|
|
52
|
+
pending: [{
|
|
53
|
+
version: 1, kind: "pending", op: "release", gate: "ship",
|
|
54
|
+
timestamp: "2026-09-05T10:00:00.000Z", expiresAt: "2026-09-07T10:00:00.000Z",
|
|
55
|
+
}],
|
|
56
|
+
resolutions: [{ version: 1, op: "release", gate: "ship", resolvedBy: "alex", timestamp: "2026-09-05T11:00:00.000Z" }],
|
|
57
|
+
});
|
|
58
|
+
const result = await runOpLocally(
|
|
59
|
+
op([
|
|
60
|
+
{ kind: "activity", fn: "plan" },
|
|
61
|
+
{ kind: "gate", gate: "ship" },
|
|
62
|
+
{ kind: "activity", fn: "apply" },
|
|
63
|
+
]),
|
|
64
|
+
activities,
|
|
65
|
+
PROFILES,
|
|
66
|
+
undefined,
|
|
67
|
+
{ gates: port, now: NOW, runId: "r2" },
|
|
68
|
+
);
|
|
69
|
+
expect(result.status).toBe("ok");
|
|
70
|
+
expect(seen[0]!.passedGates).toEqual([]);
|
|
71
|
+
expect(seen[1]!.passedGates).toEqual([
|
|
72
|
+
{ gate: "ship", approval: { gate: "ship", resolvedBy: "alex", timestamp: "2026-09-05T11:00:00.000Z" } },
|
|
73
|
+
]);
|
|
74
|
+
expect(seen[1]!.passedGates[0]!.approval).toEqual(result.records.find((r) => r.fn === "gate:ship")?.approval);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe("a policy permit and the gates a run passed (#2512)", () => {
|
|
78
|
+
const TEXT = 'permit (principal is Chant::Agent, action, resource) when { context.risk == "low" };\n';
|
|
79
|
+
const POLICY: GatePolicyRef = { kind: "gate-policy", lexicon: "cedar", name: "ship", version: gatePolicyVersion(TEXT), text: TEXT };
|
|
80
|
+
const enforce: ResolvedGateApproval = { policy: POLICY, mode: "enforce" };
|
|
81
|
+
const permit = (mode: "log-only" | "enforce"): GateResolutionRecord => ({
|
|
82
|
+
version: 1, op: "release", gate: "ship", resolvedBy: "release-bot", timestamp: "2026-09-05T11:00:00.000Z",
|
|
83
|
+
approver: { kind: "agent" },
|
|
84
|
+
policyDecision: { policy: "ship", version: POLICY.version, mode, decision: "allow", determining: ["ship-0"], errors: [] },
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
async function run(mode: "log-only" | "enforce") {
|
|
88
|
+
const seen: OpRunContext[] = [];
|
|
89
|
+
const activities = new Map<string, ActivityFn>([["apply", async () => { seen.push(currentOpRun()!); }]]);
|
|
90
|
+
const port = memoryGateLedgerPort({
|
|
91
|
+
pending: [{
|
|
92
|
+
version: 1, kind: "pending", op: "release", gate: "ship",
|
|
93
|
+
timestamp: "2026-09-05T10:00:00.000Z", expiresAt: "2026-09-07T10:00:00.000Z",
|
|
94
|
+
approval: { ...enforce, mode },
|
|
95
|
+
}],
|
|
96
|
+
resolutions: [permit(mode)],
|
|
97
|
+
});
|
|
98
|
+
const result = await runOpLocally(
|
|
99
|
+
op([
|
|
100
|
+
{ kind: "gate", gate: "ship", approval: { policy: POLICY, mode: "enforce" } },
|
|
101
|
+
{ kind: "activity", fn: "apply" },
|
|
102
|
+
]),
|
|
103
|
+
activities,
|
|
104
|
+
PROFILES,
|
|
105
|
+
undefined,
|
|
106
|
+
{ gates: port, now: NOW, runId: "r3" },
|
|
107
|
+
);
|
|
108
|
+
return { result, seen };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
test("an enforce-mode permit passes the gate, and the next step sees it as passed by policy", async () => {
|
|
112
|
+
const { result, seen } = await run("enforce");
|
|
113
|
+
expect(result.status).toBe("ok");
|
|
114
|
+
expect(seen[0]!.passedGates.map((g) => [g.gate, g.approval.via])).toEqual([["ship", "policy"]]);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("a permit recorded under log-only does not pass the enforced gate, so no step sees it as passed", async () => {
|
|
118
|
+
const { result, seen } = await run("log-only");
|
|
119
|
+
expect(result.status).toBe("gated");
|
|
120
|
+
expect(seen).toEqual([]);
|
|
121
|
+
expect(result.records.find((r) => r.fn === "gate:ship")?.approval).toBeUndefined();
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("two runs in flight at once each see their own context", async () => {
|
|
126
|
+
const seen = new Map<string, string | undefined>();
|
|
127
|
+
const activities = new Map<string, ActivityFn>([
|
|
128
|
+
["deploy", async () => {
|
|
129
|
+
await new Promise((r) => setTimeout(r, 5));
|
|
130
|
+
const ctx = currentOpRun()!;
|
|
131
|
+
seen.set(ctx.runId, ctx.env);
|
|
132
|
+
}],
|
|
133
|
+
]);
|
|
134
|
+
const config = op([{ kind: "activity", fn: "deploy" }]);
|
|
135
|
+
await Promise.all([
|
|
136
|
+
runOpLocally(config, activities, PROFILES, undefined, { runId: "a", env: "staging" }),
|
|
137
|
+
runOpLocally(config, activities, PROFILES, undefined, { runId: "b", env: "prod" }),
|
|
138
|
+
]);
|
|
139
|
+
expect(Object.fromEntries(seen)).toEqual({ a: "staging", b: "prod" });
|
|
140
|
+
});
|
|
141
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The run an activity is part of (#2522).
|
|
3
|
+
*
|
|
4
|
+
* An activity is called as `fn(args, signal)`, which says nothing about the
|
|
5
|
+
* run around it. An activity that deploys somewhere needs the environment the
|
|
6
|
+
* run was asked for, one that appends a ledger record of its own needs the run
|
|
7
|
+
* id to name the run that made it, and a step after a gate may need to know the
|
|
8
|
+
* gate passed and who approved it.
|
|
9
|
+
*
|
|
10
|
+
* `runOpLocally` (`./local-executor.ts`) sets this for the whole run, and
|
|
11
|
+
* {@link currentOpRun} reads it from inside an activity. It is backed by
|
|
12
|
+
* `AsyncLocalStorage`, so it follows the activity through every `await` and is
|
|
13
|
+
* `undefined` outside a run.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
17
|
+
import type { StepRecord } from "./local-executor";
|
|
18
|
+
|
|
19
|
+
/** A gate this run has passed, with the approval that passed it. */
|
|
20
|
+
export interface PassedGate {
|
|
21
|
+
gate: string;
|
|
22
|
+
approval: NonNullable<StepRecord["approval"]>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** What an activity can read about the run it is in. */
|
|
26
|
+
export interface OpRunContext {
|
|
27
|
+
/** The Op's name. */
|
|
28
|
+
op: string;
|
|
29
|
+
/** The run id the run ledger record carries. */
|
|
30
|
+
runId: string;
|
|
31
|
+
/** `--env`, as handed to `OpRuntimeProvider.start`. Absent when the run was started without one. */
|
|
32
|
+
env?: string;
|
|
33
|
+
/** Every gate this run has passed so far, in the order it passed them. */
|
|
34
|
+
passedGates: PassedGate[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const store = new AsyncLocalStorage<OpRunContext>();
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The run the calling activity is part of, or `undefined` outside a run.
|
|
41
|
+
* `passedGates` is a copy taken at the time of the call.
|
|
42
|
+
*/
|
|
43
|
+
export function currentOpRun(): OpRunContext | undefined {
|
|
44
|
+
const ctx = store.getStore();
|
|
45
|
+
return ctx ? { ...ctx, passedGates: [...ctx.passedGates] } : undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Run `fn` with `ctx` as the current run. The executor calls this. A runtime that calls activities itself can too. */
|
|
49
|
+
export function withOpRunContext<T>(ctx: OpRunContext, fn: () => T): T {
|
|
50
|
+
return store.run(ctx, fn);
|
|
51
|
+
}
|
|
@@ -81,6 +81,20 @@ describe("the local op runtime", () => {
|
|
|
81
81
|
expect(status.records?.map((r) => r.fn)).toEqual(["ok"]);
|
|
82
82
|
});
|
|
83
83
|
|
|
84
|
+
test("an activity reads --env and the run id the ledger records (#2522)", async () => {
|
|
85
|
+
const { currentOpRun } = await import("../run-context");
|
|
86
|
+
let seen: ReturnType<typeof currentOpRun>;
|
|
87
|
+
loadActivitiesMock.mockResolvedValue(new Map([["ok", vi.fn(async () => { seen = currentOpRun(); })]]));
|
|
88
|
+
const runtime = createLocalOpRuntime();
|
|
89
|
+
|
|
90
|
+
const handle = await runtime.start(op("hello", [{ kind: "activity", fn: "ok", args: {} }]), { env: "staging" });
|
|
91
|
+
const status = await handle.result();
|
|
92
|
+
|
|
93
|
+
expect(seen?.env).toBe("staging");
|
|
94
|
+
expect(seen?.runId).toBe(handle.runId);
|
|
95
|
+
expect(status.result?.record?.id).toBe(handle.runId);
|
|
96
|
+
});
|
|
97
|
+
|
|
84
98
|
test("the project's configured lexicons decide which appliers load", async () => {
|
|
85
99
|
loadActivitiesMock.mockResolvedValue(new Map([["ok", vi.fn(async () => ({}))]]));
|
|
86
100
|
const runtime = createLocalOpRuntime();
|
package/src/op/runtimes/local.ts
CHANGED
|
@@ -152,6 +152,8 @@ export function createLocalOpRuntime(opts: { projectPath?: string } = {}): OpRun
|
|
|
152
152
|
startOpts.signal,
|
|
153
153
|
{
|
|
154
154
|
runId,
|
|
155
|
+
// #2522: `--env` reaches the activities through the run context.
|
|
156
|
+
...(startOpts.env !== undefined ? { env: startOpts.env } : {}),
|
|
155
157
|
// The run's outcome is a durable fact (#2118): the executor
|
|
156
158
|
// appends it here, at the one seam every local run passes
|
|
157
159
|
// through, rather than in the CLI handler above it.
|
package/src/op/steward-beside.ts
CHANGED
|
@@ -118,6 +118,8 @@ export function inProcessBesideLauncher(
|
|
|
118
118
|
try {
|
|
119
119
|
const result = await runOpLocally(start.op, activities, profiles, controller.signal, {
|
|
120
120
|
steward: start.steward,
|
|
121
|
+
// #2522: the same `--env` the spawned launcher passes on the command line.
|
|
122
|
+
...(start.env ? { env: start.env } : {}),
|
|
121
123
|
ledger: { cwd: start.cwd },
|
|
122
124
|
work: { holder: start.holder },
|
|
123
125
|
});
|
package/src/op/types.ts
CHANGED
|
@@ -91,8 +91,12 @@ export interface OpWorkLease {
|
|
|
91
91
|
ttl?: string;
|
|
92
92
|
/**
|
|
93
93
|
* How the release records a run that ended `ok`: a reference to a step
|
|
94
|
-
* output holding
|
|
95
|
-
*
|
|
94
|
+
* output holding one of the closed list of lease outcomes
|
|
95
|
+
* (`WORK_LEASE_OUTCOMES` in `../lifecycle/work-lease.ts`, #3147: `done`,
|
|
96
|
+
* `not_done`, `abandoned`, `gated`, `waiting`, `dropped`, `redraft`,
|
|
97
|
+
* `ask`). Default `done`; a failed run releases `not_done`, a gated one
|
|
98
|
+
* `gated` and one waiting on a decision point `waiting`. A word outside
|
|
99
|
+
* the list is released `not_done`, with a note naming it.
|
|
96
100
|
*/
|
|
97
101
|
outcome?: StepOutputRef;
|
|
98
102
|
}
|
|
@@ -178,7 +182,8 @@ export interface ActivityStep {
|
|
|
178
182
|
*
|
|
179
183
|
* `from` is a dot-path into the return value (e.g. `"drifted"` for
|
|
180
184
|
* `{ drifted: boolean }`); when omitted, the whole return value is
|
|
181
|
-
* recorded.
|
|
185
|
+
* recorded. An attribute whose path the result does not hold (an optional
|
|
186
|
+
* field left out) is not recorded, rather than recorded as `undefined`.
|
|
182
187
|
*/
|
|
183
188
|
outcomeAttribute?: OutcomeAttribute | OutcomeAttribute[];
|
|
184
189
|
}
|
|
@@ -342,4 +342,51 @@ describe("an Op that changes the checkout", () => {
|
|
|
342
342
|
expect(git(["status", "--porcelain"], dir).stdout).toBe(" M app/index.ts\n");
|
|
343
343
|
});
|
|
344
344
|
});
|
|
345
|
+
|
|
346
|
+
test("a run that ends not_done keeps its attempt at refs/chant/kept/<item>/<token>, committed and uncommitted work, and the branch goes back (#3147)", async () => {
|
|
347
|
+
await withTestDir(async (dir) => {
|
|
348
|
+
await initRepo(dir);
|
|
349
|
+
const head = git(["rev-parse", "HEAD"], dir).stdout.trim();
|
|
350
|
+
const activities = new Map<string, ActivityFn>([
|
|
351
|
+
[
|
|
352
|
+
"build",
|
|
353
|
+
async (args) => {
|
|
354
|
+
const worktree = String(args.worktree);
|
|
355
|
+
writeFileSync(join(worktree, "app", "feature.ts"), "export const feature = true;\n");
|
|
356
|
+
expect(git(["add", "-A"], worktree).status).toBe(0);
|
|
357
|
+
expect(git(["commit", "-q", "-m", "half a build"], worktree).status).toBe(0);
|
|
358
|
+
writeFileSync(join(worktree, "app", "notes.md"), "left uncommitted\n");
|
|
359
|
+
throw new Error("the check fails");
|
|
360
|
+
},
|
|
361
|
+
],
|
|
362
|
+
]);
|
|
363
|
+
const result = await run(dispatchOp({ workLease: { item: "W-8" }, changesCheckout: true }, [step("build", { worktree: workLeaseOutput("worktree") })]), activities, dir);
|
|
364
|
+
expect(result.status).toBe("fail");
|
|
365
|
+
const token = result.workLease!.token!;
|
|
366
|
+
const kept = `refs/chant/kept/W-8/${token}`;
|
|
367
|
+
expect(result.workLease).toMatchObject({ item: "W-8", released: true, outcome: "not_done", kept });
|
|
368
|
+
// Both the commit and the uncommitted file are in the kept attempt; the branch is where the run started.
|
|
369
|
+
expect(git(["show", `${kept}:app/feature.ts`], dir).stdout).toBe("export const feature = true;\n");
|
|
370
|
+
expect(git(["show", `${kept}:app/notes.md`], dir).stdout).toBe("left uncommitted\n");
|
|
371
|
+
expect(git(["rev-parse", "chant/work/W-8"], dir).stdout.trim()).toBe(head);
|
|
372
|
+
expect(git(["rev-parse", "HEAD"], dir).stdout.trim()).toBe(head);
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
test("a run whose step names a lease outcome releases it, and keeps nothing when it changed nothing; a word outside the list is not_done (#3147)", async () => {
|
|
377
|
+
await withTestDir(async (dir) => {
|
|
378
|
+
await initRepo(dir);
|
|
379
|
+
let word = "dropped";
|
|
380
|
+
const activities = new Map<string, ActivityFn>([["record", async () => ({ outcome: word })]]);
|
|
381
|
+
const op = () => dispatchOp({ workLease: { item: "W-9", outcome: stepOutput("record", "outcome") }, changesCheckout: true }, [step("record", {}, { id: "record" })]);
|
|
382
|
+
const dropped = await run(op(), activities, dir);
|
|
383
|
+
expect(dropped.workLease).toMatchObject({ released: true, outcome: "dropped", kept: null });
|
|
384
|
+
word = "skipped";
|
|
385
|
+
const other = await run(op(), activities, dir);
|
|
386
|
+
expect(other.workLease).toMatchObject({ released: true, outcome: "not_done", kept: null });
|
|
387
|
+
const last = (await readLeaseHistory("W-9", { cwd: dir })).records.at(-1);
|
|
388
|
+
expect(last).toMatchObject({ event: "release", outcome: "not_done" });
|
|
389
|
+
expect(last?.note).toMatch(/"skipped" is not a work lease outcome/);
|
|
390
|
+
});
|
|
391
|
+
});
|
|
345
392
|
});
|
package/src/op/work-lease-run.ts
CHANGED
|
@@ -24,9 +24,11 @@
|
|
|
24
24
|
* the lease is still this run's, with the same token. Refused, the run is
|
|
25
25
|
* recorded as failed with `lease-lost`, never as done.
|
|
26
26
|
* - Release. When the run ends, the lease is given back with the run's
|
|
27
|
-
* outcome (`done`, `not_done`, `gated`, or the Op's own
|
|
28
|
-
* lands on `_leases/<id>.jsonl`.
|
|
29
|
-
*
|
|
27
|
+
* outcome (`done`, `not_done`, `gated`, `waiting`, or the Op's own
|
|
28
|
+
* `outcome`), which lands on `_leases/<id>.jsonl`. Outcomes are a closed
|
|
29
|
+
* list (`WORK_LEASE_OUTCOMES`, #3147): an Op's own outcome outside it is
|
|
30
|
+
* released `not_done`, with a note naming it. A lost lease is not
|
|
31
|
+
* released: it isn't this run's to give back.
|
|
30
32
|
*
|
|
31
33
|
* Steps read the lease through {@link workLeaseOutput}: its item, holder,
|
|
32
34
|
* token and expiry, and for an Op that changes the checkout, the worktree and
|
|
@@ -42,6 +44,13 @@
|
|
|
42
44
|
* worktree is removed when the run ends and the branch keeps whatever the
|
|
43
45
|
* steps committed, for the runner to deliver. The checkout a coding agent is
|
|
44
46
|
* editing, its index and its uncommitted files, are never touched.
|
|
47
|
+
*
|
|
48
|
+
* A run that ends `not_done` keeps its attempt aside (#3147): whatever the
|
|
49
|
+
* steps committed on the branch since the run started, and anything they
|
|
50
|
+
* left uncommitted in the worktree, is committed to
|
|
51
|
+
* `refs/chant/kept/[<member>/]<item>/<token>`, and the branch goes back to
|
|
52
|
+
* where the run started. The next attempt starts clean, and the earlier one
|
|
53
|
+
* stays readable by its lease token (`chant workspace work history`).
|
|
45
54
|
*/
|
|
46
55
|
|
|
47
56
|
import { existsSync, rmSync } from "node:fs";
|
|
@@ -50,6 +59,8 @@ import { getRuntime } from "../runtime-adapter";
|
|
|
50
59
|
import {
|
|
51
60
|
claimWorkLease,
|
|
52
61
|
DEFAULT_WORK_LEASE_TTL_MS,
|
|
62
|
+
isWorkLeaseOutcome,
|
|
63
|
+
keptAttemptRef,
|
|
53
64
|
releaseWorkLease,
|
|
54
65
|
renewWorkLease,
|
|
55
66
|
workLeaseRef,
|
|
@@ -97,8 +108,10 @@ export interface WorkLeaseRunResult {
|
|
|
97
108
|
branch: string | null;
|
|
98
109
|
/** Whether the lease was given back. False when it was lost, or never claimed. */
|
|
99
110
|
released: boolean;
|
|
100
|
-
/** The outcome the release recorded, or null when there was no release. */
|
|
111
|
+
/** The outcome the release recorded, one of `WORK_LEASE_OUTCOMES`, or null when there was no release. */
|
|
101
112
|
outcome: string | null;
|
|
113
|
+
/** For an Op that changes the checkout and ended `not_done`: the ref that keeps the attempt's work, or null when there was nothing to keep. */
|
|
114
|
+
kept: string | null;
|
|
102
115
|
/** Why the lease was lost, or null when it wasn't. */
|
|
103
116
|
lost: string | null;
|
|
104
117
|
/** Why nothing was claimed, or null when something was. */
|
|
@@ -159,6 +172,8 @@ export class RunWorkLease {
|
|
|
159
172
|
attempted = false;
|
|
160
173
|
lease: WorkLeaseOutput | undefined;
|
|
161
174
|
refusal: string | undefined;
|
|
175
|
+
/** For an Op that changes the checkout: the branch's commit when the run started, and the members its branch is scoped by. */
|
|
176
|
+
private start: { base: string; members: readonly string[] } | undefined;
|
|
162
177
|
lost: string | undefined;
|
|
163
178
|
|
|
164
179
|
private readonly spec: NonNullable<OpConfig["workLease"]>;
|
|
@@ -277,6 +292,8 @@ export class RunWorkLease {
|
|
|
277
292
|
? await git(["worktree", "add", "--quiet", worktree, branch], cwd)
|
|
278
293
|
: await git(["worktree", "add", "--quiet", "-b", branch, worktree, "HEAD"], cwd);
|
|
279
294
|
if (!add.ok) throw new Error(`Op "${this.config.name}": could not make the worktree for ${id} on ${branch}: ${add.stderr}`);
|
|
295
|
+
const base = await git(["rev-parse", "HEAD"], worktree);
|
|
296
|
+
if (base.ok) this.start = { base: base.stdout, members };
|
|
280
297
|
return { worktree, branch };
|
|
281
298
|
}
|
|
282
299
|
|
|
@@ -365,28 +382,73 @@ export class RunWorkLease {
|
|
|
365
382
|
branch: lease?.branch ?? null,
|
|
366
383
|
released: false,
|
|
367
384
|
outcome: null,
|
|
385
|
+
kept: null,
|
|
368
386
|
lost: this.lost ?? null,
|
|
369
387
|
refusal: this.refusal ?? null,
|
|
370
388
|
};
|
|
371
389
|
if (!lease) return base;
|
|
390
|
+
let outcome = status === "ok" ? "done" : status === "gated" || status === "waiting" ? status : "not_done";
|
|
391
|
+
let note = `op ${this.config.name}`;
|
|
392
|
+
if (status === "ok" && this.spec.outcome && isStepOutputRef(this.spec.outcome)) {
|
|
393
|
+
const declared = resolvePath(resultsById.get(this.spec.outcome.step), this.spec.outcome.path);
|
|
394
|
+
if (typeof declared === "string" && declared.trim() !== "") {
|
|
395
|
+
const word = declared.trim();
|
|
396
|
+
if (isWorkLeaseOutcome(word)) outcome = word;
|
|
397
|
+
else {
|
|
398
|
+
outcome = "not_done";
|
|
399
|
+
note = `op ${this.config.name}: its outcome ${JSON.stringify(word.slice(0, 80))} is not a work lease outcome`;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
let kept: string | null = null;
|
|
404
|
+
if (lease.worktree && this.lost === undefined && outcome === "not_done") {
|
|
405
|
+
kept = await this.keepAttempt(lease).catch(() => null);
|
|
406
|
+
}
|
|
372
407
|
if (lease.worktree) {
|
|
373
408
|
await git(["worktree", "remove", "--force", lease.worktree], this.opts.cwd).catch(() => undefined);
|
|
374
409
|
rmSync(lease.worktree, { recursive: true, force: true });
|
|
375
410
|
await git(["worktree", "prune"], this.opts.cwd).catch(() => undefined);
|
|
376
411
|
}
|
|
377
412
|
if (this.lost !== undefined) return base;
|
|
378
|
-
let outcome = status === "ok" ? "done" : status === "gated" || status === "waiting" ? status : "not_done";
|
|
379
|
-
if (status === "ok" && this.spec.outcome && isStepOutputRef(this.spec.outcome)) {
|
|
380
|
-
const declared = resolvePath(resultsById.get(this.spec.outcome.step), this.spec.outcome.path);
|
|
381
|
-
if (typeof declared === "string" && declared.trim() !== "") outcome = declared.trim();
|
|
382
|
-
}
|
|
383
413
|
const released = await releaseWorkLease(lease.item, this.holder, {
|
|
384
414
|
cwd: this.ledgerCwd,
|
|
385
415
|
token: lease.token,
|
|
386
416
|
outcome,
|
|
387
|
-
note
|
|
417
|
+
note,
|
|
388
418
|
}).catch(() => undefined);
|
|
389
|
-
return { ...base, released: released?.ok === true, outcome: released?.ok ? outcome : null };
|
|
419
|
+
return { ...base, kept, released: released?.ok === true, outcome: released?.ok ? outcome : null };
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Keep a `not_done` attempt aside (#3147): commit what the steps left in
|
|
424
|
+
* the worktree, committed or not, at `refs/chant/kept/.../<item>/<token>`,
|
|
425
|
+
* and move the branch back to where the run started. Returns the ref, or
|
|
426
|
+
* null when the attempt changed nothing.
|
|
427
|
+
*/
|
|
428
|
+
private async keepAttempt(lease: WorkLeaseOutput): Promise<string | null> {
|
|
429
|
+
const dir = lease.worktree;
|
|
430
|
+
const start = this.start;
|
|
431
|
+
if (!dir || !start) return null;
|
|
432
|
+
const head = await git(["rev-parse", "HEAD"], dir);
|
|
433
|
+
if (!head.ok) return null;
|
|
434
|
+
if (!(await git(["add", "-A"], dir)).ok) return null;
|
|
435
|
+
const tree = await git(["write-tree"], dir);
|
|
436
|
+
const headTree = await git(["rev-parse", `${head.stdout}^{tree}`], dir);
|
|
437
|
+
if (!tree.ok || !headTree.ok) return null;
|
|
438
|
+
let commit = head.stdout;
|
|
439
|
+
if (tree.stdout !== headTree.stdout) {
|
|
440
|
+
// An identity only when git has none, so a configured one is kept.
|
|
441
|
+
const ident = (await git(["var", "GIT_COMMITTER_IDENT"], dir)).ok && (await git(["var", "GIT_AUTHOR_IDENT"], dir)).ok;
|
|
442
|
+
const who = ident ? [] : ["-c", "user.name=chant", "-c", "user.email=chant@localhost"];
|
|
443
|
+
const made = await git([...who, "commit-tree", tree.stdout, "-p", head.stdout, "-m", `${lease.item}: not done, kept (lease ${lease.token})`], dir);
|
|
444
|
+
if (!made.ok) return null;
|
|
445
|
+
commit = made.stdout;
|
|
446
|
+
}
|
|
447
|
+
if (commit === start.base) return null;
|
|
448
|
+
const ref = keptAttemptRef(lease.item, lease.token, start.members);
|
|
449
|
+
if (!(await git(["update-ref", ref, commit], dir)).ok) return null;
|
|
450
|
+
await git(["reset", "-q", "--hard", start.base], dir);
|
|
451
|
+
return ref;
|
|
390
452
|
}
|
|
391
453
|
}
|
|
392
454
|
|
package/src/provenance.ts
CHANGED
|
@@ -177,6 +177,41 @@ export function originOfPath(
|
|
|
177
177
|
return best === undefined ? undefined : paths[best];
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
+
const INTERPOLATION_FIELDS = Symbol.for("chant.interpolationFields");
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* For an entity a tagged template built: which of its `props` paths each
|
|
184
|
+
* interpolation fed, by interpolation index (chant #3212). Element `i` lists
|
|
185
|
+
* the paths the `i`-th `${...}` landed in, in {@link EntityProvenance.paths}'
|
|
186
|
+
* grammar (dotted names, no array segments). An interpolation that fed no
|
|
187
|
+
* field the author typed, such as a reference to another entity, lists none.
|
|
188
|
+
*
|
|
189
|
+
* Only the tag can say this. It is the tag that parsed the text, so a
|
|
190
|
+
* `${props.ttlDays}` inside `TTL ts + INTERVAL ${props.ttlDays} DAY` is known
|
|
191
|
+
* to have fed `ttl` only there. When a composite factory interpreted at fold
|
|
192
|
+
* time writes the template, `discovery/fold-import.ts` joins these paths with
|
|
193
|
+
* the factory parameters each interpolation reads, which is what lets drift on
|
|
194
|
+
* such a field be reported against the parameter. A tag that never calls this
|
|
195
|
+
* leaves its fields `unknown`, the honest answer, rather than claiming either.
|
|
196
|
+
*
|
|
197
|
+
* Stored like provenance itself: non-enumerable and symbol-keyed, so no
|
|
198
|
+
* serializer, spread or comparison of the output ever sees it.
|
|
199
|
+
*/
|
|
200
|
+
export function setInterpolationFields(entity: object, fields: ReadonlyArray<readonly string[]>): void {
|
|
201
|
+
if (!Object.isExtensible(entity)) return;
|
|
202
|
+
Object.defineProperty(entity, INTERPOLATION_FIELDS, {
|
|
203
|
+
value: Object.freeze(fields.map((paths) => Object.freeze([...paths]))),
|
|
204
|
+
enumerable: false,
|
|
205
|
+
writable: true,
|
|
206
|
+
configurable: true,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** What {@link setInterpolationFields} recorded on an entity, if anything. */
|
|
211
|
+
export function getInterpolationFields(entity: object): ReadonlyArray<readonly string[]> | undefined {
|
|
212
|
+
return (entity as Record<symbol, unknown>)[INTERPOLATION_FIELDS] as ReadonlyArray<readonly string[]> | undefined;
|
|
213
|
+
}
|
|
214
|
+
|
|
180
215
|
/** One-line rendering of an origin, for diff output. */
|
|
181
216
|
export function describePathOrigin(origin: PathOrigin): string {
|
|
182
217
|
switch (origin.kind) {
|