@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
|
@@ -10,8 +10,11 @@
|
|
|
10
10
|
* the ownership tag on `arm` (chant #1448 — previously `--mode Complete`, which
|
|
11
11
|
* removed resources chant never applied), the ownership label on `gcp`
|
|
12
12
|
* (chant #1449), the metadata marker (machines) and managed app
|
|
13
|
-
* (volumes/ips/certs/secrets) on `fly` (chant #1449),
|
|
14
|
-
* `
|
|
13
|
+
* (volumes/ips/certs/secrets) on `fly` (chant #1449), the ownership labels on
|
|
14
|
+
* `grafana` (chant #3011), the comment marker on `clickhouse` (chant #3208)
|
|
15
|
+
* and `postgres` (chant #3280),
|
|
16
|
+
* and the stack itself on `cloudformation`, which a
|
|
17
|
+
* resource CFN did not create is not in. All of
|
|
15
18
|
* them are owned-only. See {@link DeleteMode}.
|
|
16
19
|
*
|
|
17
20
|
* A gate is a fact, not a wait (#2119): the local executor reads the gate
|
|
@@ -52,14 +55,18 @@ import { defaultOutput, hasNativeRollback, type ApplyTarget, type DeleteMode } f
|
|
|
52
55
|
export interface ApplyOpConfig {
|
|
53
56
|
/** Op name (kebab-case). */
|
|
54
57
|
name: string;
|
|
55
|
-
/** Environment — CFN stack name / ARM resource group / kube context env
|
|
56
|
-
*
|
|
58
|
+
/** Environment — CFN stack name / ARM resource group / kube context env /
|
|
59
|
+
* the `grafana.profiles.<env>` entry on `grafana` / the `sql.profiles.<env>`
|
|
60
|
+
* entry on `clickhouse` and `postgres`; a log label on `gcp` and
|
|
61
|
+
* `fly`. */
|
|
57
62
|
env: string;
|
|
58
63
|
/** Native apply mechanism. Default: "kubectl". */
|
|
59
64
|
target?: ApplyTarget;
|
|
60
65
|
/** Built manifest/template path. Default per target: `dist` (dir) for kubectl,
|
|
61
66
|
* `template.json` (file) for CloudFormation/ARM, `dist/gcp.yaml` for gcp,
|
|
62
|
-
* `dist/fly.json` for fly.
|
|
67
|
+
* `dist/fly.json` for fly, `dist/grafana.json` (the build index) for
|
|
68
|
+
* grafana, `dist/schema.json` for clickhouse and postgres. Must match your
|
|
69
|
+
* build output. */
|
|
63
70
|
output?: string;
|
|
64
71
|
/** Project directory to build. Default: ".". */
|
|
65
72
|
path?: string;
|
|
@@ -157,6 +157,33 @@ describe("ApplyOp: shape", () => {
|
|
|
157
157
|
expect(applyStep.fn).toBe("nativeApply");
|
|
158
158
|
expect(applyStep.args).toEqual({ target: "kubectl", env: "prod", output: "dist", deleteMode: "never" });
|
|
159
159
|
});
|
|
160
|
+
|
|
161
|
+
test("target clickhouse applies the sql build output, dist/schema.json by default (#3208)", () => {
|
|
162
|
+
const { op } = ApplyOp({ name: "p", env: "prod", target: "clickhouse", delete: "owned-only" });
|
|
163
|
+
const phases = getProps(op).phases as Array<Record<string, unknown>>;
|
|
164
|
+
const applyStep = (phases[2].steps as Array<Record<string, unknown>>)[0];
|
|
165
|
+
expect(applyStep.fn).toBe("nativeApply");
|
|
166
|
+
expect(applyStep.args).toEqual({ target: "clickhouse", env: "prod", output: "dist/schema.json", deleteMode: "owned-only" });
|
|
167
|
+
expect(getProps(op).onFailure).toBeUndefined();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("target postgres applies the sql build output, dist/schema.json by default (#3280)", () => {
|
|
171
|
+
const { op } = ApplyOp({ name: "p", env: "prod", target: "postgres", delete: "owned-only" });
|
|
172
|
+
const phases = getProps(op).phases as Array<Record<string, unknown>>;
|
|
173
|
+
const applyStep = (phases[2].steps as Array<Record<string, unknown>>)[0];
|
|
174
|
+
expect(applyStep.fn).toBe("nativeApply");
|
|
175
|
+
expect(applyStep.args).toEqual({ target: "postgres", env: "prod", output: "dist/schema.json", deleteMode: "owned-only" });
|
|
176
|
+
expect(getProps(op).onFailure).toBeUndefined();
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("target grafana applies the build index, dist/grafana.json by default (#3011)", () => {
|
|
180
|
+
const { op } = ApplyOp({ name: "p", env: "prod", target: "grafana", delete: "owned-only" });
|
|
181
|
+
const phases = getProps(op).phases as Array<Record<string, unknown>>;
|
|
182
|
+
const applyStep = (phases[2].steps as Array<Record<string, unknown>>)[0];
|
|
183
|
+
expect(applyStep.fn).toBe("nativeApply");
|
|
184
|
+
expect(applyStep.args).toEqual({ target: "grafana", env: "prod", output: "dist/grafana.json", deleteMode: "owned-only" });
|
|
185
|
+
expect(getProps(op).onFailure).toBeUndefined();
|
|
186
|
+
});
|
|
160
187
|
});
|
|
161
188
|
|
|
162
189
|
describe("ApplyOp: gating + deletes", () => {
|
|
@@ -274,7 +301,7 @@ describe("ApplyOp: compensation (#125, total-or-refused in #1449)", () => {
|
|
|
274
301
|
});
|
|
275
302
|
|
|
276
303
|
test("compensate: true is refused at build time for every rollback-less target (#1449)", () => {
|
|
277
|
-
for (const target of ["kubectl", "kustomize", "arm", "gcp", "fly"] as const) {
|
|
304
|
+
for (const target of ["kubectl", "kustomize", "arm", "gcp", "fly", "grafana", "clickhouse", "postgres"] as const) {
|
|
278
305
|
expect(() => ApplyOp({ name: "p", env: "prod", target, compensate: true })).toThrow(
|
|
279
306
|
new RegExp(`ApplyOp "p": compensate is enabled, but target "${target}" has no automatic rollback`),
|
|
280
307
|
);
|
|
@@ -296,7 +323,7 @@ describe("ApplyOp: compensation (#125, total-or-refused in #1449)", () => {
|
|
|
296
323
|
});
|
|
297
324
|
|
|
298
325
|
test("an object without a command is refused the same way as true", () => {
|
|
299
|
-
for (const target of ["kubectl", "kustomize", "arm", "gcp", "fly"] as const) {
|
|
326
|
+
for (const target of ["kubectl", "kustomize", "arm", "gcp", "fly", "grafana", "clickhouse", "postgres"] as const) {
|
|
300
327
|
expect(() => ApplyOp({ name: "p", env: "prod", target, compensate: {} })).toThrow(
|
|
301
328
|
/has no automatic rollback/,
|
|
302
329
|
);
|
|
@@ -304,7 +331,7 @@ describe("ApplyOp: compensation (#125, total-or-refused in #1449)", () => {
|
|
|
304
331
|
});
|
|
305
332
|
|
|
306
333
|
test("a command lifts the refusal on every target", () => {
|
|
307
|
-
for (const target of ["kubectl", "kustomize", "arm", "gcp", "fly", "cloudformation"] as const) {
|
|
334
|
+
for (const target of ["kubectl", "kustomize", "arm", "gcp", "fly", "grafana", "clickhouse", "postgres", "cloudformation"] as const) {
|
|
308
335
|
const { op } = ApplyOp({
|
|
309
336
|
name: "p",
|
|
310
337
|
env: "prod",
|
|
@@ -49,17 +49,22 @@ describe("LexiconUpgradeOp composite (#527)", () => {
|
|
|
49
49
|
expect(phases[0].steps[0].outcomeAttribute).toEqual({ name: "HasUpgrade", from: "hasUpgrade" });
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
-
test("
|
|
52
|
+
test("known set is exactly the 11 lexicons (helm/forgejo excluded)", () => {
|
|
53
53
|
// cedar joined with #1650: it pins `CEDAR_WASM_VERSION`, a dependency
|
|
54
54
|
// rather than a downloaded spec, but the pinned-constant machinery the Op
|
|
55
55
|
// drives is identical. helm/forgejo still have no upstream at all.
|
|
56
56
|
expect([...IN_SCOPE_LEXICONS].sort()).toEqual(
|
|
57
|
-
["aws", "azure", "cedar", "docker", "fly", "gcp", "github", "gitlab", "k8s"].sort(),
|
|
57
|
+
["aws", "azure", "cedar", "docker", "fly", "gcp", "github", "gitlab", "k3s", "k8s", "sql"].sort(),
|
|
58
58
|
);
|
|
59
59
|
expect(IN_SCOPE_LEXICONS).not.toContain("helm");
|
|
60
60
|
expect(IN_SCOPE_LEXICONS).not.toContain("forgejo");
|
|
61
61
|
});
|
|
62
62
|
|
|
63
|
+
test("accepts a pinned lexicon by name, discovered at run time", () => {
|
|
64
|
+
expect(() => LexiconUpgradeOp({ lexicon: "sql" })).not.toThrow();
|
|
65
|
+
expect(() => LexiconUpgradeOp({ lexicon: "k3s" })).not.toThrow();
|
|
66
|
+
});
|
|
67
|
+
|
|
63
68
|
test("rejects out-of-scope lexicons at construction", () => {
|
|
64
69
|
for (const l of ["helm", "forgejo"]) {
|
|
65
70
|
expect(() => LexiconUpgradeOp({ lexicon: l as never })).toThrow(/not in scope/);
|
|
@@ -13,9 +13,13 @@
|
|
|
13
13
|
* lands on the Op itself (#2120).
|
|
14
14
|
*
|
|
15
15
|
* Lexicon classification (which check the activity dispatches to):
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* ROLLING {aws, azure, github, fly} → checkRollingUpgrade
|
|
17
|
+
* PINNED any other lexicon whose plugin declares `upstreamPin`
|
|
18
|
+
* (k8s, k3s, gcp, docker, gitlab, cedar, sql, ...) → checkPinnedUpgrade
|
|
19
|
+
* The pinned set is discovered from the plugin at run time, the way `chant dev
|
|
20
|
+
* pinned-upgrade` does. A pin that moves with other constants (`alsoMoves`, as
|
|
21
|
+
* sql's version plus digest) is report-only. helm / forgejo are excluded: no
|
|
22
|
+
* upstream spec.
|
|
19
23
|
*
|
|
20
24
|
* Finding-modes mirror ReconcileOp: `report` (default, no external services)
|
|
21
25
|
* | `issue` | `pull-request`. For the epic goal, `pull-request` opens/updates a
|
|
@@ -43,9 +47,15 @@ import { Op, phase } from "../builders";
|
|
|
43
47
|
import type { OpResource } from "../resource";
|
|
44
48
|
import type { LexiconUpgradeMode, SupportedLexicon } from "../activities/lexicon-upgrade";
|
|
45
49
|
|
|
46
|
-
/**
|
|
50
|
+
/**
|
|
51
|
+
* The lexicons known to be in scope. This is not a gate: a lexicon that declares
|
|
52
|
+
* `upstreamPin` is dispatched without being listed. helm / forgejo are excluded
|
|
53
|
+
* (no upstream spec).
|
|
54
|
+
*/
|
|
47
55
|
export const IN_SCOPE_LEXICONS: readonly SupportedLexicon[] = [
|
|
48
56
|
"k8s",
|
|
57
|
+
"k3s",
|
|
58
|
+
"sql",
|
|
49
59
|
"gcp",
|
|
50
60
|
"docker",
|
|
51
61
|
"gitlab",
|
|
@@ -74,6 +84,12 @@ export interface LexiconUpgradeOpConfig {
|
|
|
74
84
|
* `<cwd>/lexicons/<lexicon>` at run time.
|
|
75
85
|
*/
|
|
76
86
|
lexiconDir?: string;
|
|
87
|
+
/**
|
|
88
|
+
* Label of one pin to check, for a lexicon whose plugin declares several
|
|
89
|
+
* (`upstreamPins`; sql has `clickhouse` and `postgres-14` to `postgres-18`).
|
|
90
|
+
* Omit to check every pin.
|
|
91
|
+
*/
|
|
92
|
+
pin?: string;
|
|
77
93
|
/**
|
|
78
94
|
* What to produce on findings. Default: "report".
|
|
79
95
|
* @default "report"
|
|
@@ -86,15 +102,19 @@ export interface LexiconUpgradeOpResources {
|
|
|
86
102
|
op: InstanceType<typeof OpResource>;
|
|
87
103
|
}
|
|
88
104
|
|
|
105
|
+
const EXCLUDED_LEXICONS: ReadonlySet<string> = new Set(["helm", "forgejo"]);
|
|
106
|
+
|
|
89
107
|
/**
|
|
90
|
-
* Build a LexiconUpgradeOp for one lexicon. Rejects
|
|
91
|
-
* construction time
|
|
108
|
+
* Build a LexiconUpgradeOp for one lexicon. Rejects the lexicons with no
|
|
109
|
+
* upstream spec at construction time. Whether any other name declares a pin is
|
|
110
|
+
* only known by loading its plugin, so the activity checks that at run time and
|
|
111
|
+
* fails with a clear message for a name that has none.
|
|
92
112
|
*/
|
|
93
113
|
export function LexiconUpgradeOp(config: LexiconUpgradeOpConfig): LexiconUpgradeOpResources {
|
|
94
|
-
if (
|
|
114
|
+
if (EXCLUDED_LEXICONS.has(config.lexicon)) {
|
|
95
115
|
throw new Error(
|
|
96
116
|
`LexiconUpgradeOp: "${config.lexicon}" is not in scope. ` +
|
|
97
|
-
`
|
|
117
|
+
`Known lexicons: ${IN_SCOPE_LEXICONS.join(", ")}, and any lexicon declaring upstreamPin ` +
|
|
98
118
|
`(helm, forgejo have no upstream spec).`,
|
|
99
119
|
);
|
|
100
120
|
}
|
|
@@ -119,6 +139,7 @@ export function LexiconUpgradeOp(config: LexiconUpgradeOpConfig): LexiconUpgrade
|
|
|
119
139
|
args: {
|
|
120
140
|
lexicon: config.lexicon,
|
|
121
141
|
...(config.lexiconDir ? { lexiconDir: config.lexiconDir } : {}),
|
|
142
|
+
...(config.pin ? { pin: config.pin } : {}),
|
|
122
143
|
mode: onFinding,
|
|
123
144
|
},
|
|
124
145
|
// Surface whether a PR-worthy upgrade was found as the run's
|
|
@@ -39,3 +39,26 @@ describe("emulatorLifecycle command builders", () => {
|
|
|
39
39
|
expect(emu.endpoint(4599)).toBe("http://localhost:4599");
|
|
40
40
|
});
|
|
41
41
|
});
|
|
42
|
+
|
|
43
|
+
describe("an emulator reached by its own client, not over HTTP", () => {
|
|
44
|
+
const pg = emulatorLifecycle({
|
|
45
|
+
name: "chant-postgres",
|
|
46
|
+
image: "postgres:18",
|
|
47
|
+
containerPort: 5432,
|
|
48
|
+
readyCommand: ["pg_isready", "-h", "127.0.0.1"],
|
|
49
|
+
endpoint: (port) => `postgres://postgres@localhost:${port}/postgres`,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("readiness is a command run in the container", () => {
|
|
53
|
+
expect(pg.readyExecCommand("chant-postgres")).toBe("docker exec chant-postgres pg_isready -h 127.0.0.1");
|
|
54
|
+
expect(emulatorLifecycle({ name: "x", image: "x:1", containerPort: 1, healthPath: "/h" }).readyExecCommand("x")).toBeUndefined();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("the endpoint is the declared one, not an http URL", () => {
|
|
58
|
+
expect(pg.endpoint(55432)).toBe("postgres://postgres@localhost:55432/postgres");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("a spec with neither a health path nor a ready command is refused", () => {
|
|
62
|
+
expect(() => emulatorLifecycle({ name: "x", image: "x:1", containerPort: 1 })).toThrow(/neither a healthPath nor a readyCommand/);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -16,8 +16,25 @@ export interface EmulatorSpec {
|
|
|
16
16
|
image: string;
|
|
17
17
|
/** Port the emulator listens on inside the container (e.g. 4566, 4280). */
|
|
18
18
|
containerPort: number;
|
|
19
|
-
/**
|
|
20
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Health path on the host port (e.g. `/_localstack/health`, `/_mudflaps/health`).
|
|
21
|
+
* Required unless {@link readyCommand} is set: an emulator is reached over
|
|
22
|
+
* HTTP or checks itself inside its container, one or the other.
|
|
23
|
+
*/
|
|
24
|
+
healthPath?: string;
|
|
25
|
+
/**
|
|
26
|
+
* A command run inside the container (`docker exec <name> ...`) whose exit
|
|
27
|
+
* status 0 means ready, for an emulator with no HTTP health endpoint: a
|
|
28
|
+
* database server answers its own client (`pg_isready`), not a URL. When
|
|
29
|
+
* set, it replaces the HTTP poll of {@link healthPath}.
|
|
30
|
+
*/
|
|
31
|
+
readyCommand?: readonly string[];
|
|
32
|
+
/**
|
|
33
|
+
* The endpoint a tool connects to, for an emulator that is not reached over
|
|
34
|
+
* HTTP, e.g. a `postgres://` URL for a database server.
|
|
35
|
+
* Default: `http://localhost:<port>`.
|
|
36
|
+
*/
|
|
37
|
+
endpoint?: (port: number) => string;
|
|
21
38
|
/** Readiness predicate over the health body. Default: any 200 response is ready. */
|
|
22
39
|
ready?: (healthBody: string) => boolean;
|
|
23
40
|
/** Extra `docker run` args inserted before the image (e.g. a socket mount). */
|
|
@@ -102,6 +119,8 @@ export interface EmulatorLifecycle {
|
|
|
102
119
|
existsCommand(name: string): string;
|
|
103
120
|
rmCommand(name: string): string;
|
|
104
121
|
healthUrl(port: number): string;
|
|
122
|
+
/** `docker exec <name> <readyCommand...>`, or undefined for an emulator polled over HTTP. */
|
|
123
|
+
readyExecCommand(name: string): string | undefined;
|
|
105
124
|
endpoint(port: number): string;
|
|
106
125
|
up(args?: EmulatorUpArgs, signal?: AbortSignal): Promise<{ endpoint: string }>;
|
|
107
126
|
down(args?: { name?: string }, signal?: AbortSignal): Promise<void>;
|
|
@@ -116,6 +135,9 @@ export interface EmulatorLifecycle {
|
|
|
116
135
|
*/
|
|
117
136
|
export function emulatorLifecycle(spec: EmulatorSpec): EmulatorLifecycle {
|
|
118
137
|
const ready = spec.ready ?? (() => true);
|
|
138
|
+
if (spec.healthPath === undefined && !spec.readyCommand?.length) {
|
|
139
|
+
throw new Error(`emulator "${spec.name}" declares neither a healthPath nor a readyCommand, so nothing can tell when it is ready`);
|
|
140
|
+
}
|
|
119
141
|
|
|
120
142
|
const runCommand = (args: EmulatorUpArgs = {}): string => {
|
|
121
143
|
const name = args.name ?? spec.name;
|
|
@@ -128,8 +150,30 @@ export function emulatorLifecycle(spec: EmulatorSpec): EmulatorLifecycle {
|
|
|
128
150
|
};
|
|
129
151
|
const existsCommand = (name: string): string => `docker ps -q -f name=${name}`;
|
|
130
152
|
const rmCommand = (name: string): string => `docker rm -f ${name}`;
|
|
131
|
-
const healthUrl = (port: number): string => `http://localhost:${port}${spec.healthPath}`;
|
|
132
|
-
const endpoint = (port: number): string => `http://localhost:${port}
|
|
153
|
+
const healthUrl = (port: number): string => `http://localhost:${port}${spec.healthPath ?? ""}`;
|
|
154
|
+
const endpoint = (port: number): string => (spec.endpoint ? spec.endpoint(port) : `http://localhost:${port}`);
|
|
155
|
+
const readyExecCommand = (name: string): string | undefined =>
|
|
156
|
+
spec.readyCommand?.length ? ["docker", "exec", name, ...spec.readyCommand].join(" ") : undefined;
|
|
157
|
+
|
|
158
|
+
/** One readiness probe: the in-container command when declared, else the HTTP health path. */
|
|
159
|
+
async function probe(name: string, port: number, signal?: AbortSignal): Promise<boolean> {
|
|
160
|
+
const command = readyExecCommand(name);
|
|
161
|
+
if (command) {
|
|
162
|
+
try {
|
|
163
|
+
await execAsync(command, { signal });
|
|
164
|
+
return true;
|
|
165
|
+
} catch {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
try {
|
|
170
|
+
const res = await fetch(healthUrl(port), { signal });
|
|
171
|
+
return res.ok && ready(await res.text());
|
|
172
|
+
} catch {
|
|
173
|
+
// Not up yet (connection refused / non-2xx) — retry.
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
133
177
|
|
|
134
178
|
async function up(args: EmulatorUpArgs = {}, signal?: AbortSignal): Promise<{ endpoint: string }> {
|
|
135
179
|
const name = args.name ?? spec.name;
|
|
@@ -153,19 +197,13 @@ export function emulatorLifecycle(spec: EmulatorSpec): EmulatorLifecycle {
|
|
|
153
197
|
await execAsync(runCommand({ ...args, name, port }), { signal });
|
|
154
198
|
}
|
|
155
199
|
|
|
156
|
-
const url = healthUrl(port);
|
|
157
200
|
const deadline = Date.now() + timeoutMs;
|
|
158
201
|
let ok = false;
|
|
159
202
|
while (Date.now() < deadline) {
|
|
160
203
|
if (signal?.aborted) throw new Error(`emulator "${name}" wait aborted`);
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
ok = true;
|
|
165
|
-
break;
|
|
166
|
-
}
|
|
167
|
-
} catch {
|
|
168
|
-
// Not up yet (connection refused / non-2xx) — retry.
|
|
204
|
+
if (await probe(name, port, signal)) {
|
|
205
|
+
ok = true;
|
|
206
|
+
break;
|
|
169
207
|
}
|
|
170
208
|
await sleep(intervalMs, signal);
|
|
171
209
|
}
|
|
@@ -187,5 +225,5 @@ export function emulatorLifecycle(spec: EmulatorSpec): EmulatorLifecycle {
|
|
|
187
225
|
}
|
|
188
226
|
}
|
|
189
227
|
|
|
190
|
-
return { runCommand, existsCommand, rmCommand, healthUrl, endpoint, up, down };
|
|
228
|
+
return { runCommand, existsCommand, rmCommand, healthUrl, readyExecCommand, endpoint, up, down };
|
|
191
229
|
}
|
|
@@ -107,6 +107,23 @@ describe("gate approval — quorum (#2508)", () => {
|
|
|
107
107
|
expect(port.appended).toHaveLength(0);
|
|
108
108
|
});
|
|
109
109
|
|
|
110
|
+
// #2547: a gate still pending, and an approval given, under the bare sha256: prefix
|
|
111
|
+
// keep working once the run computes the same plan's digest as jcs1-sha256:.
|
|
112
|
+
test("a pending gate and an approval recorded under the bare prefix match the jcs1 digest of the same plan", async () => {
|
|
113
|
+
const jcs = `jcs1-${PLAN_A}`;
|
|
114
|
+
const block: ResolvedGateApproval = { quorum: { count: 1 }, mode: "log-only" };
|
|
115
|
+
const run = async (resolutions: GateResolutionRecord[]) => {
|
|
116
|
+
const port = memoryGateLedgerPort({ resolutions, pending: [{ ...PENDING, approval: block }] });
|
|
117
|
+
return { port, check: await evaluateGate(port, { op: "release", gate: "ship", planDigest: jcs, approval: block, now: NOW }) };
|
|
118
|
+
};
|
|
119
|
+
const approved = await run([approval({ resolvedBy: "alex", timestamp: "2026-09-01T01:00:00.000Z" })]);
|
|
120
|
+
expect(approved.check.satisfied).toBe(true);
|
|
121
|
+
// A standing fact is not rewritten just because the prefix moved.
|
|
122
|
+
const waiting = await run([]);
|
|
123
|
+
expect(waiting.check.satisfied).toBe(false);
|
|
124
|
+
expect(waiting.port.appended).toHaveLength(0);
|
|
125
|
+
});
|
|
126
|
+
|
|
110
127
|
test("an approval older than the standing pending fact does not count", async () => {
|
|
111
128
|
const { check } = await decide(
|
|
112
129
|
[approval({ resolvedBy: "alex", timestamp: "2026-08-31T00:00:00.000Z" })],
|
|
@@ -174,6 +191,80 @@ describe("gate approval — policy decisions (#2508)", () => {
|
|
|
174
191
|
expect(check.satisfied).toBe(false);
|
|
175
192
|
});
|
|
176
193
|
|
|
194
|
+
test("log-only then enforce: a permit recorded under log-only does not pass the enforced gate (#2512)", async () => {
|
|
195
|
+
const logOnly: ResolvedGateApproval = { policy: POLICY, mode: "log-only", context: { risk: "low" } };
|
|
196
|
+
const enforce: ResolvedGateApproval = { ...logOnly, mode: "enforce" };
|
|
197
|
+
// The pending fact was recorded while the gate was log-only, and the agent's allow was recorded against it.
|
|
198
|
+
const port = memoryGateLedgerPort({
|
|
199
|
+
resolutions: [approval({ resolvedBy: "release-bot", timestamp: "2026-09-01T01:00:00.000Z", approver: { kind: "agent" }, policyDecision: allow("log-only") })],
|
|
200
|
+
pending: [{ ...PENDING, approval: logOnly }],
|
|
201
|
+
});
|
|
202
|
+
const check = await evaluateGate(port, { op: "release", gate: "ship", planDigest: PLAN_A, approval: enforce, now: NOW });
|
|
203
|
+
expect(check.satisfied).toBe(false);
|
|
204
|
+
|
|
205
|
+
const tally = tallyGateApprovals(
|
|
206
|
+
[approval({ resolvedBy: "release-bot", timestamp: "2026-09-01T01:00:00.000Z", approver: { kind: "agent" }, policyDecision: allow("log-only") })],
|
|
207
|
+
"ship", PENDING.timestamp, PLAN_A, enforce,
|
|
208
|
+
);
|
|
209
|
+
expect(tally.permit).toBeUndefined();
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test("log-only then enforce: a human's approval recorded under log-only still counts toward the quorum (#2512)", async () => {
|
|
213
|
+
const logOnly: ResolvedGateApproval = { quorum: { count: 1 }, policy: POLICY, mode: "log-only" };
|
|
214
|
+
const enforce: ResolvedGateApproval = { ...logOnly, mode: "enforce" };
|
|
215
|
+
const port = memoryGateLedgerPort({
|
|
216
|
+
resolutions: [approval({ resolvedBy: "alex", timestamp: "2026-09-01T01:00:00.000Z", policyDecision: allow("log-only") })],
|
|
217
|
+
pending: [{ ...PENDING, approval: logOnly }],
|
|
218
|
+
});
|
|
219
|
+
const check = await evaluateGate(port, { op: "release", gate: "ship", planDigest: PLAN_A, approval: enforce, now: NOW });
|
|
220
|
+
expect(check.satisfied).toBe(true);
|
|
221
|
+
if (!check.satisfied) return;
|
|
222
|
+
expect(check.via).toBe("quorum");
|
|
223
|
+
expect(check.resolution.resolvedBy).toBe("alex");
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test("log-only then enforce: the gated run re-records the pending fact under enforce, and only a fresh permit passes (#2512)", async () => {
|
|
227
|
+
const logOnly: ResolvedGateApproval = { policy: POLICY, mode: "log-only", context: { risk: "low" } };
|
|
228
|
+
const enforce: ResolvedGateApproval = { ...logOnly, mode: "enforce" };
|
|
229
|
+
const stale = approval({ resolvedBy: "release-bot", timestamp: "2026-09-01T01:00:00.000Z", approver: { kind: "agent" }, policyDecision: allow("log-only") });
|
|
230
|
+
const port = memoryGateLedgerPort({ resolutions: [stale], pending: [{ ...PENDING, approval: logOnly }] });
|
|
231
|
+
|
|
232
|
+
const first = await evaluateGate(port, { op: "release", gate: "ship", planDigest: PLAN_A, approval: enforce, now: NOW });
|
|
233
|
+
expect(first.satisfied).toBe(false);
|
|
234
|
+
if (first.satisfied) return;
|
|
235
|
+
// The standing fact described the log-only gate, so a fresh one carries
|
|
236
|
+
// enforce, and `chant approve` evaluates the next agent approval under it.
|
|
237
|
+
expect(first.recorded).toBe(true);
|
|
238
|
+
expect(first.pending.approval?.mode).toBe("enforce");
|
|
239
|
+
|
|
240
|
+
// Re-running without a new approval still waits: the stale permit predates the new pending fact as well.
|
|
241
|
+
const again = await evaluateGate(port, { op: "release", gate: "ship", planDigest: PLAN_A, approval: enforce, now: "2026-09-01T12:30:00.000Z" });
|
|
242
|
+
expect(again.satisfied).toBe(false);
|
|
243
|
+
|
|
244
|
+
const fresh = approval({ resolvedBy: "release-bot", timestamp: "2026-09-01T13:00:00.000Z", approver: { kind: "agent" }, policyDecision: allow("enforce") });
|
|
245
|
+
const after = await evaluateGate(
|
|
246
|
+
memoryGateLedgerPort({ resolutions: [stale, fresh], pending: [first.pending] }),
|
|
247
|
+
{ op: "release", gate: "ship", planDigest: PLAN_A, approval: enforce, now: "2026-09-01T14:00:00.000Z" },
|
|
248
|
+
);
|
|
249
|
+
expect(after.satisfied).toBe(true);
|
|
250
|
+
if (!after.satisfied) return;
|
|
251
|
+
expect(after.via).toBe("policy");
|
|
252
|
+
expect(after.resolution.timestamp).toBe(fresh.timestamp);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test("enforce: a recorded decision with no mode does not pass the gate (#2512)", async () => {
|
|
256
|
+
const block: ResolvedGateApproval = { policy: POLICY, mode: "enforce" };
|
|
257
|
+
const { mode: _mode, ...noMode } = allow()!;
|
|
258
|
+
const { check } = await decide(
|
|
259
|
+
[approval({
|
|
260
|
+
resolvedBy: "release-bot", timestamp: "2026-09-01T01:00:00.000Z", approver: { kind: "agent" },
|
|
261
|
+
policyDecision: noMode as GateResolutionRecord["policyDecision"],
|
|
262
|
+
})],
|
|
263
|
+
block,
|
|
264
|
+
);
|
|
265
|
+
expect(check.satisfied).toBe(false);
|
|
266
|
+
});
|
|
267
|
+
|
|
177
268
|
test("enforce: a deny-by-default policy keeps the old behaviour, where only a human's approval counts", async () => {
|
|
178
269
|
const block: ResolvedGateApproval = { policy: POLICY, mode: "enforce" };
|
|
179
270
|
const deny = { ...allow()!, decision: "deny" as const, determining: ["floor"] };
|
package/src/op/gate-approval.ts
CHANGED
|
@@ -119,7 +119,11 @@ export interface GatePolicyAnswer {
|
|
|
119
119
|
export interface GatePolicyDecision extends GatePolicyAnswer {
|
|
120
120
|
policy: string;
|
|
121
121
|
version: string;
|
|
122
|
-
/**
|
|
122
|
+
/**
|
|
123
|
+
* The gate's mode when the decision was recorded. A permit passes a gate
|
|
124
|
+
* only when both this and the gate's current mode are `enforce` (#2512), so
|
|
125
|
+
* an allow recorded under `log-only` never binds after a switch.
|
|
126
|
+
*/
|
|
123
127
|
mode: GateApprovalMode;
|
|
124
128
|
}
|
|
125
129
|
|
package/src/op/gate.ts
CHANGED
|
@@ -44,7 +44,7 @@ import {
|
|
|
44
44
|
type PendingGateInput,
|
|
45
45
|
type PendingGateRecord,
|
|
46
46
|
} from "../lifecycle/gate-ledger";
|
|
47
|
-
import { describePlanDigest } from "../lifecycle/plan-digest";
|
|
47
|
+
import { describePlanDigest, samePlanDigest } from "../lifecycle/plan-digest";
|
|
48
48
|
import { isModelAuthored } from "../lifecycle/gate-origin";
|
|
49
49
|
import { sortedJsonReplacer } from "../utils";
|
|
50
50
|
import type { GateApprover, ResolvedGateApproval } from "./gate-approval";
|
|
@@ -74,6 +74,19 @@ export interface PendingGatePush {
|
|
|
74
74
|
export interface GateLedgerPort {
|
|
75
75
|
read(op: string): Promise<{ resolutions: GateResolutionRecord[]; pending: PendingGateRecord[] }>;
|
|
76
76
|
appendPending(input: PendingGateInput): Promise<PendingGatePush>;
|
|
77
|
+
/**
|
|
78
|
+
* The rule a workspace sets for approvals of `gate` (#3163, ws-080), or
|
|
79
|
+
* null when none applies: the declaration's `identity.gates`, read at base.
|
|
80
|
+
* Absent, every approval is judged as before.
|
|
81
|
+
*/
|
|
82
|
+
approvalRule?(gate: string): Promise<GateApprovalRule | null>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** A workspace's rule for which approvals of a gate count (#3163): a signed approval, from a class when it names one. */
|
|
86
|
+
export interface GateApprovalRule {
|
|
87
|
+
requirement: { gate: string; class: string | null };
|
|
88
|
+
/** Why `approval` doesn't count, or null when it does. */
|
|
89
|
+
refuses(approval: GateResolutionRecord): string | null;
|
|
77
90
|
}
|
|
78
91
|
|
|
79
92
|
/**
|
|
@@ -102,6 +115,11 @@ export function gitGateLedgerPort(opts?: { cwd?: string }): GateLedgerPort {
|
|
|
102
115
|
const { resolutions, pending } = await readGateLedger(op, opts);
|
|
103
116
|
return { resolutions, pending };
|
|
104
117
|
},
|
|
118
|
+
async approvalRule(gate) {
|
|
119
|
+
// Loaded on demand: the rule lives in the workspace modules, which import the Op modules.
|
|
120
|
+
const { gateAdmission } = await import("../workspace/identity");
|
|
121
|
+
return gateAdmission(opts?.cwd ?? process.cwd(), gate);
|
|
122
|
+
},
|
|
105
123
|
async appendPending(input) {
|
|
106
124
|
const { record } = await appendPendingGate(input, opts);
|
|
107
125
|
try {
|
|
@@ -288,7 +306,7 @@ export interface GateTally {
|
|
|
288
306
|
counted: GateResolutionRecord[];
|
|
289
307
|
/** The quorum's count, 1 when the gate declares none. */
|
|
290
308
|
need: number;
|
|
291
|
-
/** The newest approval whose recorded permit passes the gate on its own. Only in `enforce` mode, and only under the gate's current policy version. */
|
|
309
|
+
/** The newest approval whose recorded permit passes the gate on its own. Only in `enforce` mode, only for a permit recorded under `enforce`, and only under the gate's current policy version. */
|
|
292
310
|
permit?: GateResolutionRecord;
|
|
293
311
|
/** The newest approval of this gate for a different plan, when the gate binds one. */
|
|
294
312
|
mismatched?: GateResolutionRecord;
|
|
@@ -305,8 +323,9 @@ export interface GateTally {
|
|
|
305
323
|
* Of those, only a human's counts toward the quorum, once per `resolvedBy`,
|
|
306
324
|
* and only with one of the quorum's roles when it names any. An agent counts
|
|
307
325
|
* only through a recorded `allow` in `enforce` mode, evaluated under the
|
|
308
|
-
* policy version the gate declares now
|
|
309
|
-
*
|
|
326
|
+
* policy version the gate declares now and recorded while the gate was in
|
|
327
|
+
* `enforce` (#2512). A `log-only` decision never changes the outcome, even
|
|
328
|
+
* after the gate switches to `enforce`, and a `deny` never removes a human's approval: a policy can add
|
|
310
329
|
* a way through the gate but cannot take one away.
|
|
311
330
|
*/
|
|
312
331
|
export function tallyGateApprovals(
|
|
@@ -325,14 +344,18 @@ export function tallyGateApprovals(
|
|
|
325
344
|
let mismatched: GateResolutionRecord | undefined;
|
|
326
345
|
for (const r of records) {
|
|
327
346
|
if (r.gate !== gate || at(r) < since) continue;
|
|
328
|
-
if (planDigest !== undefined && r.planDigest
|
|
347
|
+
if (planDigest !== undefined && !samePlanDigest(r.planDigest, planDigest)) {
|
|
329
348
|
if (!mismatched || at(r) >= at(mismatched)) mismatched = r;
|
|
330
349
|
continue;
|
|
331
350
|
}
|
|
332
351
|
|
|
333
352
|
const decision = r.policyDecision;
|
|
353
|
+
// #2512: the decision must also have been recorded under `enforce`. An
|
|
354
|
+
// allow recorded while the gate was log-only was never binding, and a
|
|
355
|
+
// later switch to enforce does not make it binding.
|
|
334
356
|
if (
|
|
335
357
|
approval.mode === "enforce" && approval.policy && decision?.decision === "allow" &&
|
|
358
|
+
decision.mode === "enforce" &&
|
|
336
359
|
decision.version === approval.policy.version && (!permit || at(r) >= at(permit))
|
|
337
360
|
) {
|
|
338
361
|
permit = r;
|
|
@@ -394,10 +417,13 @@ export async function evaluateGate(port: GateLedgerPort, input: GateCheckInput):
|
|
|
394
417
|
// a mismatch with a message instead of silently not counting.
|
|
395
418
|
const env = input.environment;
|
|
396
419
|
const pending = env === undefined ? ledger.pending : ledger.pending.filter((p) => p.environment === env);
|
|
397
|
-
|
|
420
|
+
// #3163: a gate the workspace's identity.gates names counts only signed approvals from its class.
|
|
421
|
+
const rule = (await port.approvalRule?.(input.gate)) ?? null;
|
|
422
|
+
const resolutions = (
|
|
398
423
|
env === undefined
|
|
399
424
|
? ledger.resolutions
|
|
400
|
-
: ledger.resolutions.filter((r) => r.environment === undefined || r.environment === env)
|
|
425
|
+
: ledger.resolutions.filter((r) => r.environment === undefined || r.environment === env)
|
|
426
|
+
).filter((r) => rule === null || r.gate !== input.gate || rule.refuses(r) === null);
|
|
401
427
|
|
|
402
428
|
const standing = latestPendingGate(pending, input.gate);
|
|
403
429
|
const since = standing?.timestamp ?? EPOCH;
|
|
@@ -447,7 +473,7 @@ export async function evaluateGate(port: GateLedgerPort, input: GateCheckInput):
|
|
|
447
473
|
// under another policy version or another context would have `chant
|
|
448
474
|
// approve` evaluate the policy against something this run no longer has.
|
|
449
475
|
if (
|
|
450
|
-
standing && !isPendingGateExpired(standing, now) && standing.planDigest
|
|
476
|
+
standing && !isPendingGateExpired(standing, now) && samePlanDigest(standing.planDigest, input.planDigest) &&
|
|
451
477
|
sameApproval(standing.approval, input.approval)
|
|
452
478
|
) {
|
|
453
479
|
return { satisfied: false, pending: standing, recorded: false, ...asMismatch };
|
package/src/op/index.ts
CHANGED
|
@@ -60,6 +60,8 @@ export type { ActivityProfile, ActivityProfileName } from "./activity-profiles";
|
|
|
60
60
|
export { NonRetryableActivityError, nonRetryableFailure } from "./activity-failure";
|
|
61
61
|
export { runOpLocally, parseDuration, OpRunFailure } from "./local-executor";
|
|
62
62
|
export type { StepRecord, OpRunResult, RunOpOptions } from "./local-executor";
|
|
63
|
+
export { currentOpRun, withOpRunContext } from "./run-context";
|
|
64
|
+
export type { OpRunContext, PassedGate } from "./run-context";
|
|
63
65
|
export { evaluateGate, gitGateLedgerPort, memoryGateLedgerPort, approveCommand, describeGateMismatch } from "./gate";
|
|
64
66
|
export type { GateLedgerPort, GateCheck, GateCheckInput, PendingGatePush, GateDigestMismatch, GateQuorumProgress, GateTally } from "./gate";
|
|
65
67
|
export { tallyGateApprovals, approverOf } from "./gate";
|
|
@@ -71,7 +73,7 @@ export type {
|
|
|
71
73
|
GatePolicyEvaluator, GatePolicyRef, GatePolicyRequest, GateQuorum, ResolvedGateApproval,
|
|
72
74
|
} from "./gate-approval";
|
|
73
75
|
export {
|
|
74
|
-
computePlanDigest, isPlanDigest, describePlanDigest, PLAN_DIGEST_ALGORITHM,
|
|
76
|
+
computePlanDigest, isPlanDigest, describePlanDigest, samePlanDigest, PLAN_DIGEST_ALGORITHM, PLAN_DIGEST_PREFIX,
|
|
75
77
|
} from "../lifecycle/plan-digest";
|
|
76
78
|
export { gateName, usesDeprecatedGateKey, DEPRECATED_GATE_KEY_WARNING } from "./gate-name";
|
|
77
79
|
export type { GateNamed } from "./gate-name";
|
|
@@ -232,6 +232,21 @@ describe("runOpLocally — outcomeAttribute", () => {
|
|
|
232
232
|
expect(result.records[0].outcome).toEqual({ name: "Drift", value: true });
|
|
233
233
|
});
|
|
234
234
|
|
|
235
|
+
test("an attribute whose path the result does not hold is left off, not recorded as undefined", async () => {
|
|
236
|
+
const done: ActivityFn = async () => ({ state: "done", switched: false });
|
|
237
|
+
const config = op({
|
|
238
|
+
phases: [{ name: "Switch", steps: [
|
|
239
|
+
{ kind: "activity", fn: "migrationSwitch", outcomeAttribute: [{ name: "OldColumn", from: "oldColumn" }, { name: "State", from: "state" }] },
|
|
240
|
+
{ kind: "activity", fn: "migrationRetain", outcomeAttribute: { name: "RetainUntil", from: "retainUntil" } },
|
|
241
|
+
] }],
|
|
242
|
+
});
|
|
243
|
+
const result = await runOpLocally(config, new Map([["migrationSwitch", done], ["migrationRetain", done]]), PROFILES);
|
|
244
|
+
expect(result.records[0].outcomes).toEqual([{ name: "State", value: "done" }]);
|
|
245
|
+
expect(result.records[0].outcome).toEqual({ name: "State", value: "done" });
|
|
246
|
+
expect(result.records[1].outcomes).toBeUndefined();
|
|
247
|
+
expect(result.records[1].outcome).toBeUndefined();
|
|
248
|
+
});
|
|
249
|
+
|
|
235
250
|
test("a step with no attribute leaves both fields absent", async () => {
|
|
236
251
|
const noop: ActivityFn = async () => ({ ok: true });
|
|
237
252
|
const config = op({ phases: [{ name: "Plan", steps: [{ kind: "activity", fn: "noop" }] }] });
|