@intentius/chant 0.124.12 → 0.124.14
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/bin/chant +11 -0
- package/dist/agents/checks.js +326 -0
- package/dist/agents/discover.js +885 -0
- package/dist/agents/importer.js +17 -0
- package/dist/agents/index.js +12 -0
- package/dist/agents/types.js +19 -0
- package/dist/apply.js +151 -0
- package/dist/attrref.js +70 -0
- package/dist/audit/catalog.js +350 -0
- package/dist/audit/core.js +252 -0
- package/dist/audit/discover.js +633 -0
- package/dist/audit/fetch.js +666 -0
- package/dist/audit/lineage.js +91 -0
- package/dist/audit/nginx.js +366 -0
- package/dist/audit/prior-art.js +132 -0
- package/dist/audit/proof.js +321 -0
- package/dist/audit/report-html.js +194 -0
- package/dist/audit/report-model.js +198 -0
- package/dist/audit/report.js +102 -0
- package/dist/audit/rules-doc.js +144 -0
- package/dist/audit/secrets.js +405 -0
- package/dist/audit/terraform-state.js +140 -0
- package/dist/audit/wrangler.js +262 -0
- package/dist/behaviour-delta.js +401 -0
- package/dist/behaviour-engine.js +357 -0
- package/dist/behaviour-graph.js +83 -0
- package/dist/behaviour-http.js +188 -0
- package/dist/behaviour-kinds.js +172 -0
- package/dist/behaviour-overlay.js +50 -0
- package/dist/behaviour-predict.js +188 -0
- package/dist/behaviour-request.js +194 -0
- package/dist/behaviour.js +1325 -0
- package/dist/build-params.js +108 -0
- package/dist/build.js +668 -0
- package/dist/builder.js +44 -0
- package/dist/cdk/advise.js +30 -0
- package/dist/cdk/assembly.js +122 -0
- package/dist/cdk/graph.js +444 -0
- package/dist/cdk/tier-map.js +64 -0
- package/dist/cdk/types.js +24 -0
- package/dist/change-set.js +312 -0
- package/dist/child-project.js +21 -0
- package/dist/claimed-fields.js +135 -0
- package/dist/cli/build-options.js +70 -0
- package/dist/cli/build-params-cli.js +139 -0
- package/dist/cli/command-group.js +123 -0
- package/dist/cli/commands/audit-agents.js +326 -0
- package/dist/cli/commands/audit.js +571 -0
- package/dist/cli/commands/build.js +920 -0
- package/dist/cli/commands/carve-apply.js +153 -0
- package/dist/cli/commands/carve-bridge.js +201 -0
- package/dist/cli/commands/carve-emit.js +332 -0
- package/dist/cli/commands/carve-status.js +119 -0
- package/dist/cli/commands/carve.js +204 -0
- package/dist/cli/commands/check-lexicon-docs.js +79 -0
- package/dist/cli/commands/check-lexicon-examples.js +177 -0
- package/dist/cli/commands/check-lexicon-intrinsics.js +291 -0
- package/dist/cli/commands/check-lexicon-mcp.js +82 -0
- package/dist/cli/commands/check-lexicon-plugin.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon-plugin.js +104 -0
- package/dist/cli/commands/check-lexicon.js +766 -0
- package/dist/cli/commands/describe.js +85 -0
- package/dist/cli/commands/diff.js +175 -0
- package/dist/cli/commands/doctor.js +350 -0
- package/dist/cli/commands/import-agents.js +155 -0
- package/dist/cli/commands/import.js +670 -0
- package/dist/cli/commands/init-lexicon/templates/codegen.js +194 -0
- package/dist/cli/commands/init-lexicon/templates/docs.js +100 -0
- package/dist/cli/commands/init-lexicon/templates/examples.js +29 -0
- package/dist/cli/commands/init-lexicon/templates/lint.js +34 -0
- package/dist/cli/commands/init-lexicon/templates/lsp.js +37 -0
- package/dist/cli/commands/init-lexicon/templates/plugin.js +108 -0
- package/dist/cli/commands/init-lexicon/templates/project.js +240 -0
- package/dist/cli/commands/init-lexicon/templates/spec.js +56 -0
- package/dist/cli/commands/init-lexicon/templates/tests.js +66 -0
- package/dist/cli/commands/init-lexicon.js +172 -0
- package/dist/cli/commands/init.js +381 -0
- package/dist/cli/commands/lexicon-rolling-upgrade.js +145 -0
- package/dist/cli/commands/lexicon-surface-diff.js +145 -0
- package/dist/cli/commands/licenses.js +125 -0
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/commands/lint.js +948 -0
- package/dist/cli/commands/list.js +63 -0
- package/dist/cli/commands/migrate.d.ts.map +1 -1
- package/dist/cli/commands/migrate.js +423 -0
- package/dist/cli/commands/onboard.js +403 -0
- package/dist/cli/commands/pinned-upgrade.js +191 -0
- package/dist/cli/commands/update.js +194 -0
- package/dist/cli/commands/vendor.js +217 -0
- package/dist/cli/conflict-check.js +104 -0
- package/dist/cli/debug.js +8 -0
- package/dist/cli/format.js +104 -0
- package/dist/cli/handlers/build.js +267 -0
- package/dist/cli/handlers/carve-apply.js +30 -0
- package/dist/cli/handlers/carve-bridge.js +28 -0
- package/dist/cli/handlers/carve-emit.js +50 -0
- package/dist/cli/handlers/carve-status.js +26 -0
- package/dist/cli/handlers/carve.js +40 -0
- package/dist/cli/handlers/change-set.js +46 -0
- package/dist/cli/handlers/components.js +730 -0
- package/dist/cli/handlers/dev.js +113 -0
- package/dist/cli/handlers/emulator.js +233 -0
- package/dist/cli/handlers/explain.js +54 -0
- package/dist/cli/handlers/fan-out.js +422 -0
- package/dist/cli/handlers/gate-resume.js +275 -0
- package/dist/cli/handlers/generate.js +84 -0
- package/dist/cli/handlers/graph.js +969 -0
- package/dist/cli/handlers/init.js +102 -0
- package/dist/cli/handlers/lifecycle.js +1727 -0
- package/dist/cli/handlers/lint.js +62 -0
- package/dist/cli/handlers/migrate.js +48 -0
- package/dist/cli/handlers/misc.js +391 -0
- package/dist/cli/handlers/operator.js +1162 -0
- package/dist/cli/handlers/override-approve.js +102 -0
- package/dist/cli/handlers/pr.js +503 -0
- package/dist/cli/handlers/promote.js +342 -0
- package/dist/cli/handlers/run-generate.js +206 -0
- package/dist/cli/handlers/run-wave.js +66 -0
- package/dist/cli/handlers/run.js +1191 -0
- package/dist/cli/handlers/scenario.js +325 -0
- package/dist/cli/handlers/search.js +924 -0
- package/dist/cli/handlers/serve.js +32 -0
- package/dist/cli/handlers/vendor.js +154 -0
- package/dist/cli/index.js +3 -0
- package/dist/cli/is-entry-point.js +23 -0
- package/dist/cli/lsp/capabilities.js +28 -0
- package/dist/cli/lsp/diagnostics.js +34 -0
- package/dist/cli/lsp/server.js +329 -0
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +2540 -0
- package/dist/cli/mcp/lifecycle-tools.js +80 -0
- package/dist/cli/mcp/op-tools.js +306 -0
- package/dist/cli/mcp/resource-handlers.js +258 -0
- package/dist/cli/mcp/resources/context.js +85 -0
- package/dist/cli/mcp/server.js +353 -0
- package/dist/cli/mcp/tools/build.js +111 -0
- package/dist/cli/mcp/tools/composites.js +95 -0
- package/dist/cli/mcp/tools/explain.js +166 -0
- package/dist/cli/mcp/tools/import.js +49 -0
- package/dist/cli/mcp/tools/lint.js +41 -0
- package/dist/cli/mcp/tools/scaffold.js +111 -0
- package/dist/cli/mcp/tools/search.js +112 -0
- package/dist/cli/mcp/types.js +1 -0
- package/dist/cli/mcp/workspace-plugins.js +91 -0
- package/dist/cli/mcp/workspace-tools.d.ts +1 -1
- package/dist/cli/mcp/workspace-tools.js +564 -0
- package/dist/cli/mcp-config.js +70 -0
- package/dist/cli/plugins.js +313 -0
- package/dist/cli/registry.js +23 -0
- package/dist/cli/reporters/stylish.js +329 -0
- package/dist/cli/version.js +15 -0
- package/dist/cli/watch.js +79 -0
- package/dist/codegen/bound-property-types.js +82 -0
- package/dist/codegen/coverage.js +119 -0
- package/dist/codegen/docs-file-markers.js +60 -0
- package/dist/codegen/docs-pages.js +115 -0
- package/dist/codegen/docs-rule-scanning.js +164 -0
- package/dist/codegen/docs-rules.js +76 -0
- package/dist/codegen/docs-sections.js +117 -0
- package/dist/codegen/docs-sidebar.js +65 -0
- package/dist/codegen/docs-types.js +4 -0
- package/dist/codegen/docs.js +356 -0
- package/dist/codegen/fetch.js +340 -0
- package/dist/codegen/generate-post-synth-barrel.js +63 -0
- package/dist/codegen/generate-registry.js +71 -0
- package/dist/codegen/generate-runtime-index.js +56 -0
- package/dist/codegen/generate-typescript.js +119 -0
- package/dist/codegen/generate.js +115 -0
- package/dist/codegen/json-patch.js +141 -0
- package/dist/codegen/json-schema-to-ts.js +344 -0
- package/dist/codegen/json-schema.js +246 -0
- package/dist/codegen/lexicon-regen.js +412 -0
- package/dist/codegen/naming.js +217 -0
- package/dist/codegen/okf-lexicon.js +245 -0
- package/dist/codegen/package.js +150 -0
- package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
- package/dist/codegen/pinned-upgrade.js +494 -0
- package/dist/codegen/registry.js +60 -0
- package/dist/codegen/rehype-base-url.mjs +68 -0
- package/dist/codegen/rolling-upgrade.js +217 -0
- package/dist/codegen/surface-snapshot.js +537 -0
- package/dist/codegen/testdata/postgres-tags.json +242 -0
- package/dist/codegen/topo-sort.js +36 -0
- package/dist/codegen/typecheck.js +115 -0
- package/dist/codegen/validate.js +200 -0
- package/dist/codegen/version-bump.js +35 -0
- package/dist/components/auto-release.js +143 -0
- package/dist/components/builders.js +47 -0
- package/dist/components/capability-plugin-loader.js +175 -0
- package/dist/components/capability-plugin-schema.js +46 -0
- package/dist/components/capability-plugin.js +99 -0
- package/dist/components/capability.js +60 -0
- package/dist/components/changed-files.js +72 -0
- package/dist/components/cli-support.js +427 -0
- package/dist/components/component.js +120 -0
- package/dist/components/component.schema.json +271 -0
- package/dist/components/config-defaults.js +130 -0
- package/dist/components/deploy-units.js +60 -0
- package/dist/components/discover.d.ts.map +1 -1
- package/dist/components/discover.js +189 -0
- package/dist/components/driver-output.js +86 -0
- package/dist/components/driver.js +728 -0
- package/dist/components/fan-out-output.js +165 -0
- package/dist/components/fan-out-record.js +94 -0
- package/dist/components/fan-out-run.js +373 -0
- package/dist/components/fan-out-support.js +60 -0
- package/dist/components/fan-out.js +273 -0
- package/dist/components/gate-plan.js +41 -0
- package/dist/components/gated-wave-pipeline.js +46 -0
- package/dist/components/index.js +24 -0
- package/dist/components/layers.js +28 -0
- package/dist/components/manifest-persistence.js +108 -0
- package/dist/components/pilots/alb-ecs.pilot.js +59 -0
- package/dist/components/pilots/dynamodb.pilot.js +51 -0
- package/dist/components/pilots/jar-emr.pilot.js +65 -0
- package/dist/components/pilots/lambda.pilot.js +52 -0
- package/dist/components/pilots/neo4j-fanout.pilot.js +68 -0
- package/dist/components/pilots/project.js +12 -0
- package/dist/components/pr-pipeline.js +134 -0
- package/dist/components/pr-run.js +363 -0
- package/dist/components/presets/ecs-fargate.js +72 -0
- package/dist/components/presets/index.js +28 -0
- package/dist/components/presets/lambda.js +34 -0
- package/dist/components/presets/single-host-compose.js +41 -0
- package/dist/components/promote.js +549 -0
- package/dist/components/registry.js +34 -0
- package/dist/components/run-progress.js +33 -0
- package/dist/components/sandbox/driver.d.ts.map +1 -1
- package/dist/components/sandbox/driver.js +93 -0
- package/dist/components/sandbox/run.js +115 -0
- package/dist/components/starter-plugin.js +104 -0
- package/dist/components/verbs/__tests__/mock-cloud-executor.js +68 -0
- package/dist/components/verbs/__tests__/mock-process-runner.js +45 -0
- package/dist/components/verbs/__tests__/mock-sbom-generator.js +56 -0
- package/dist/components/verbs/bom-writer.js +287 -0
- package/dist/components/verbs/build-archive.js +188 -0
- package/dist/components/verbs/build.js +142 -0
- package/dist/components/verbs/cloud-executor.js +110 -0
- package/dist/components/verbs/component-bom.js +125 -0
- package/dist/components/verbs/ensure-secret.js +50 -0
- package/dist/components/verbs/index.js +61 -0
- package/dist/components/verbs/license-policy.js +75 -0
- package/dist/components/verbs/lockfile-sbom-generator.js +191 -0
- package/dist/components/verbs/process-runner.js +78 -0
- package/dist/components/verbs/r2-sync.js +93 -0
- package/dist/components/verbs/reproducibility.js +62 -0
- package/dist/components/verbs/run-agent.js +577 -0
- package/dist/components/verbs/sbom-generator.js +119 -0
- package/dist/components/verbs/sbom.js +97 -0
- package/dist/components/verbs/shell.js +32 -0
- package/dist/components/verbs/sign.js +221 -0
- package/dist/components/verbs/stub.js +28 -0
- package/dist/components/verbs/tool-sbom-generator.js +154 -0
- package/dist/components/verbs/verify.js +112 -0
- package/dist/components/verbs/vex.js +131 -0
- package/dist/components/verbs/vuln-gate.js +152 -0
- package/dist/components/verbs/vuln-scan.js +288 -0
- package/dist/components/verbs/wait-verify.js +106 -0
- package/dist/components/verbs/wrangler.js +179 -0
- package/dist/components/wave-plan.js +102 -0
- package/dist/composite.js +305 -0
- package/dist/config-import.d.ts.map +1 -1
- package/dist/config-import.js +22 -0
- package/dist/config-sandbox.js +122 -0
- package/dist/config-static.js +206 -0
- package/dist/config.js +605 -0
- package/dist/content-digest.js +23 -0
- package/dist/declarable.js +114 -0
- package/dist/deep-observation.js +437 -0
- package/dist/detectLexicon.js +54 -0
- package/dist/discovery/cache.js +70 -0
- package/dist/discovery/collect.js +208 -0
- package/dist/discovery/cycles.js +95 -0
- package/dist/discovery/entity-wire-codec.js +391 -0
- package/dist/discovery/entity-wire.js +42 -0
- package/dist/discovery/files.js +121 -0
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/fold-import.js +4178 -0
- package/dist/discovery/fold-rank.js +292 -0
- package/dist/discovery/graph.js +115 -0
- package/dist/discovery/import.d.ts.map +1 -1
- package/dist/discovery/import.js +65 -0
- package/dist/discovery/index.js +333 -0
- package/dist/discovery/param-deps.js +487 -0
- package/dist/discovery/resolve.js +39 -0
- package/dist/discovery/sandbox/bundle.js +214 -0
- package/dist/discovery/sandbox/child-errors.js +26 -0
- package/dist/discovery/sandbox/config-run.js +100 -0
- package/dist/discovery/sandbox/config-wire.js +183 -0
- package/dist/discovery/sandbox/driver.d.ts.map +1 -1
- package/dist/discovery/sandbox/driver.js +333 -0
- package/dist/discovery/sandbox/fork.js +229 -0
- package/dist/discovery/sandbox/policy-run.js +142 -0
- package/dist/discovery/sandbox/policy-wire.js +152 -0
- package/dist/discovery/sandbox/run.js +77 -0
- package/dist/discovery/specifier-resolve.js +320 -0
- package/dist/discovery/walk.js +284 -0
- package/dist/effect-receipt.js +340 -0
- package/dist/env.js +55 -0
- package/dist/errors.js +62 -0
- package/dist/fold/fold.js +1434 -0
- package/dist/fold/foldable-helpers.js +229 -0
- package/dist/fold/subset.js +696 -0
- package/dist/fold-provenance.js +414 -0
- package/dist/gate-decision.d.ts +36 -4
- package/dist/gate-decision.d.ts.map +1 -1
- package/dist/gate-decision.js +260 -0
- package/dist/gate-resume-run.js +154 -0
- package/dist/gated-waves.js +165 -0
- package/dist/governance.js +43 -0
- package/dist/graph-declared.js +45 -0
- package/dist/graph-detail.js +207 -0
- package/dist/graph-dot.js +54 -0
- package/dist/graph-effective.js +107 -0
- package/dist/graph-ir.js +623 -0
- package/dist/graph-layout.js +184 -0
- package/dist/graph-lens.js +105 -0
- package/dist/graph-mermaid.js +83 -0
- package/dist/graph-ops.js +39 -0
- package/dist/graph-refs.js +156 -0
- package/dist/held-elsewhere.js +87 -0
- package/dist/identity.js +251 -0
- package/dist/import/base-parser.js +34 -0
- package/dist/import/embedded.js +255 -0
- package/dist/import/generator.js +1 -0
- package/dist/import/ir-utils.js +85 -0
- package/dist/import/parser-options.js +45 -0
- package/dist/import/parser.js +1 -0
- package/dist/index.js +118 -0
- package/dist/intrinsic-interpolation.js +73 -0
- package/dist/intrinsic.js +31 -0
- package/dist/join-key.js +35 -0
- package/dist/kubectl-context.js +150 -0
- package/dist/lexicon-config.js +58 -0
- package/dist/lexicon-integrity.js +47 -0
- package/dist/lexicon-manifest.js +70 -0
- package/dist/lexicon-module.d.ts.map +1 -1
- package/dist/lexicon-module.js +241 -0
- package/dist/lexicon-output.js +218 -0
- package/dist/lexicon-plugin-helpers.js +104 -0
- package/dist/lexicon-schema.js +136 -0
- package/dist/lexicon.js +130 -0
- package/dist/lifecycle/affected.js +224 -0
- package/dist/lifecycle/assert-live.js +110 -0
- package/dist/lifecycle/build-ledger-store.js +160 -0
- package/dist/lifecycle/build-ledger.js +155 -0
- package/dist/lifecycle/change-set.js +356 -0
- package/dist/lifecycle/converge-ledger.js +161 -0
- package/dist/lifecycle/deep-diff.js +233 -0
- package/dist/lifecycle/deep-observe.js +147 -0
- package/dist/lifecycle/digest.js +143 -0
- package/dist/lifecycle/disruption.js +135 -0
- package/dist/lifecycle/gate-applied.js +113 -0
- package/dist/lifecycle/gate-audit.js +341 -0
- package/dist/lifecycle/gate-ledger-select.js +89 -0
- package/dist/lifecycle/gate-ledger.d.ts +8 -0
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.js +193 -0
- package/dist/lifecycle/gate-origin.js +117 -0
- package/dist/lifecycle/gate-pending.js +58 -0
- package/dist/lifecycle/git.js +1182 -0
- package/dist/lifecycle/identity.js +58 -0
- package/dist/lifecycle/index.js +26 -0
- package/dist/lifecycle/lease.js +283 -0
- package/dist/lifecycle/ledger-port.js +1 -0
- package/dist/lifecycle/legacy-digest.js +91 -0
- package/dist/lifecycle/live-diff.js +274 -0
- package/dist/lifecycle/local-note.js +15 -0
- package/dist/lifecycle/member-ledger.js +122 -0
- package/dist/lifecycle/observation-baseline.js +155 -0
- package/dist/lifecycle/observe.js +375 -0
- package/dist/lifecycle/oras-referrer-lookup.js +82 -0
- package/dist/lifecycle/override-ledger.js +54 -0
- package/dist/lifecycle/plan-digest.js +110 -0
- package/dist/lifecycle/plan-ledger.js +129 -0
- package/dist/lifecycle/receipt-plan.js +192 -0
- package/dist/lifecycle/release-ledger.js +155 -0
- package/dist/lifecycle/release-read.js +64 -0
- package/dist/lifecycle/replay.js +193 -0
- package/dist/lifecycle/rollback.js +102 -0
- package/dist/lifecycle/run-ledger.js +183 -0
- package/dist/lifecycle/scenario-eval.js +204 -0
- package/dist/lifecycle/scenario.js +214 -0
- package/dist/lifecycle/snapshot.js +239 -0
- package/dist/lifecycle/status.js +447 -0
- package/dist/lifecycle/symptoms.js +117 -0
- package/dist/lifecycle/teardown.js +255 -0
- package/dist/lifecycle/types.js +1 -0
- package/dist/lifecycle/unobserved-gate.js +33 -0
- package/dist/lifecycle/wave-plans.js +68 -0
- package/dist/lifecycle/work-item-id.js +36 -0
- package/dist/lifecycle/work-lease.js +393 -0
- package/dist/lint/component-checks.js +87 -0
- package/dist/lint/config.js +483 -0
- package/dist/lint/declarative.js +122 -0
- package/dist/lint/discover.d.ts.map +1 -1
- package/dist/lint/discover.js +138 -0
- package/dist/lint/engine.js +246 -0
- package/dist/lint/knowledge-checks.js +64 -0
- package/dist/lint/named-checks.js +22 -0
- package/dist/lint/output-checks.js +87 -0
- package/dist/lint/output-docs.js +163 -0
- package/dist/lint/parser.js +33 -0
- package/dist/lint/pipeline-change-gate.js +70 -0
- package/dist/lint/policy-import.d.ts +1 -1
- package/dist/lint/policy-import.d.ts.map +1 -1
- package/dist/lint/policy-import.js +62 -0
- package/dist/lint/policy-sandbox.js +86 -0
- package/dist/lint/policy.js +122 -0
- package/dist/lint/post-synth.js +87 -0
- package/dist/lint/presets/relaxed.json +26 -0
- package/dist/lint/presets/strict.json +24 -24
- package/dist/lint/receipt-checks.js +83 -0
- package/dist/lint/rule-loader.d.ts.map +1 -1
- package/dist/lint/rule-loader.js +89 -0
- package/dist/lint/rule-registry.js +86 -0
- package/dist/lint/rule.js +1 -0
- package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-a.component.js +7 -0
- package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-b.component.js +7 -0
- package/dist/lint/rules/__fixtures__/comp/comp001/fail/orphan-lib.component.js +9 -0
- package/dist/lint/rules/__fixtures__/comp/comp001/pass/emr-job.component.js +11 -0
- package/dist/lint/rules/__fixtures__/comp/comp001/pass/jar-lib.component.js +9 -0
- package/dist/lint/rules/__fixtures__/comp/comp002/fail/search-service.component.js +25 -0
- package/dist/lint/rules/__fixtures__/comp/comp002/pass/jar-lib.component.js +8 -0
- package/dist/lint/rules/__fixtures__/comp/comp002/pass/search-service.component.js +21 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/fail/migrator.component.js +8 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.js +24 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/migrator.component.js +17 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/orders-table.component.js +8 -0
- package/dist/lint/rules/__fixtures__/comp/comp004/fail/neo4j-cluster.component.js +18 -0
- package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.js +20 -0
- package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.js +8 -0
- package/dist/lint/rules/__fixtures__/comp/comp005/fail/search-service.component.js +12 -0
- package/dist/lint/rules/__fixtures__/comp/comp005/pass/search-service.component.js +16 -0
- package/dist/lint/rules/__fixtures__/comp/comp006/fail/legacy-tool.component.js +8 -0
- package/dist/lint/rules/__fixtures__/comp/comp006/pass/legacy-tool.component.js +12 -0
- package/dist/lint/rules/__fixtures__/comp/comp007/fail/inventory-table.component.js +11 -0
- package/dist/lint/rules/__fixtures__/comp/comp007/fail/orders-table.component.js +11 -0
- package/dist/lint/rules/__fixtures__/comp/comp007/pass/orders-table.component.js +10 -0
- package/dist/lint/rules/__fixtures__/comp/comp007/pass/search-service.component.js +16 -0
- package/dist/lint/rules/comp/comp001-publishes-never-applies.js +69 -0
- package/dist/lint/rules/comp/comp002-dangling-wiring-ref.js +84 -0
- package/dist/lint/rules/comp/comp003-mutating-no-rollback.js +103 -0
- package/dist/lint/rules/comp/comp004-gate-needs-durable-runtime.js +70 -0
- package/dist/lint/rules/comp/comp005-capability-kind-is-noun.js +109 -0
- package/dist/lint/rules/comp/comp006-shell-needs-reason.js +49 -0
- package/dist/lint/rules/comp/comp007-composition-sprawl.js +64 -0
- package/dist/lint/rules/comp/index.js +33 -0
- package/dist/lint/rules/comp/support.js +129 -0
- package/dist/lint/rules/composite-scope.js +51 -0
- package/dist/lint/rules/cor017-composite-name-match.js +93 -0
- package/dist/lint/rules/cor018-composite-prefer-lexicon-type.js +136 -0
- package/dist/lint/rules/cor021-env-literal-name.js +100 -0
- package/dist/lint/rules/cor022-receipt-leaf.js +118 -0
- package/dist/lint/rules/cor024-receipt-secret-pointer.js +199 -0
- package/dist/lint/rules/declarable-naming-convention.js +62 -0
- package/dist/lint/rules/evl001-non-literal-expression.js +66 -0
- package/dist/lint/rules/evl002-control-flow-resource.js +69 -0
- package/dist/lint/rules/evl003-dynamic-property-access.js +44 -0
- package/dist/lint/rules/evl004-spread-non-const.js +94 -0
- package/dist/lint/rules/evl005-resource-block-body.js +41 -0
- package/dist/lint/rules/evl007-invalid-siblings.js +105 -0
- package/dist/lint/rules/evl009-composite-no-constant.js +158 -0
- package/dist/lint/rules/evl010-composite-no-transform.js +59 -0
- package/dist/lint/rules/evl011-symbolic-in-template.js +106 -0
- package/dist/lint/rules/export-required.js +115 -0
- package/dist/lint/rules/file-declarable-limit.js +88 -0
- package/dist/lint/rules/flat-declarations.js +62 -0
- package/dist/lint/rules/index.js +90 -0
- package/dist/lint/rules/no-cyclic-declarable-ref.js +138 -0
- package/dist/lint/rules/no-redundant-type-import.js +76 -0
- package/dist/lint/rules/no-redundant-value-cast.js +41 -0
- package/dist/lint/rules/no-string-ref.js +59 -0
- package/dist/lint/rules/no-unused-declarable-import.js +75 -0
- package/dist/lint/rules/no-unused-declarable.js +142 -0
- package/dist/lint/rules/op/index.js +28 -0
- package/dist/lint/rules/op/ops012-activity-contract.js +54 -0
- package/dist/lint/rules/op/ops013-step-output-ref.js +88 -0
- package/dist/lint/rules/op/ops014-converge-rule-refusals.js +114 -0
- package/dist/lint/rules/op/ops015-gate-approval.js +45 -0
- package/dist/lint/rules/op/support.js +11 -0
- package/dist/lint/rules/op/sys010-key-over-plain-http.js +100 -0
- package/dist/lint/rules/property-kind.js +56 -0
- package/dist/lint/rules/single-concern-file.js +71 -0
- package/dist/lint/rules/sys001-no-literal-key.js +60 -0
- package/dist/lint/selectors.js +168 -0
- package/dist/lint/suppressions.js +129 -0
- package/dist/lint/typescript.js +17 -0
- package/dist/live-endpoint.js +113 -0
- package/dist/live-read-session.js +76 -0
- package/dist/lsp/lexicon-providers.js +163 -0
- package/dist/lsp/types.js +5 -0
- package/dist/managed-fields.js +293 -0
- package/dist/mcp/types.js +4 -0
- package/dist/notify-verify.js +0 -1
- package/dist/notify.js +0 -1
- package/dist/notify.schema.json +72 -0
- package/dist/observation.js +266 -0
- package/dist/okf-read.js +134 -0
- package/dist/okf.js +275 -0
- package/dist/op/__fixtures__/decide-stub-backend.js +125 -0
- package/dist/op/activities/activity-contracts.js +313 -0
- package/dist/op/activities/apply.js +582 -0
- package/dist/op/activities/build.js +18 -0
- package/dist/op/activities/change-coverage.js +40 -0
- package/dist/op/activities/change-set.js +64 -0
- package/dist/op/activities/converge.js +492 -0
- package/dist/op/activities/decide.js +211 -0
- package/dist/op/activities/env-teardown.js +110 -0
- package/dist/op/activities/factory.js +591 -0
- package/dist/op/activities/forge-pull-request.js +82 -0
- package/dist/op/activities/gh-host.js +78 -0
- package/dist/op/activities/guard-validate.js +165 -0
- package/dist/op/activities/http-check.js +40 -0
- package/dist/op/activities/index.js +59 -0
- package/dist/op/activities/lexicon-upgrade.js +629 -0
- package/dist/op/activities/lifecycle.js +109 -0
- package/dist/op/activities/pipeline-audit.js +147 -0
- package/dist/op/activities/policy.js +26 -0
- package/dist/op/activities/predict-behaviour.js +341 -0
- package/dist/op/activities/propose-upgrade.js +165 -0
- package/dist/op/activities/reconcile.js +997 -0
- package/dist/op/activities/shell.js +118 -0
- package/dist/op/activities/source-release.js +97 -0
- package/dist/op/activities/source-rollback.js +165 -0
- package/dist/op/activities/teardown.js +17 -0
- package/dist/op/activities/util.js +6 -0
- package/dist/op/activities/wait.js +51 -0
- package/dist/op/activities/work-evidence.js +35 -0
- package/dist/op/activities/workflow-audit.js +133 -0
- package/dist/op/activity-contract-registry.js +106 -0
- package/dist/op/activity-contract.js +232 -0
- package/dist/op/activity-failure.js +31 -0
- package/dist/op/activity-profiles.js +129 -0
- package/dist/op/activity-registry.js +105 -0
- package/dist/op/activity-runtime.js +28 -0
- package/dist/op/builders.js +801 -0
- package/dist/op/change-signal.js +118 -0
- package/dist/op/composites/apply-op.js +151 -0
- package/dist/op/composites/behaviour-op.js +65 -0
- package/dist/op/composites/converge-op.js +141 -0
- package/dist/op/composites/index.js +19 -0
- package/dist/op/composites/lexicon-upgrade-op.js +107 -0
- package/dist/op/composites/pipeline-audit-op.js +50 -0
- package/dist/op/composites/reconcile-op.js +84 -0
- package/dist/op/composites/watch-op.js +69 -0
- package/dist/op/composites/workflow-audit-op.js +57 -0
- package/dist/op/converge-rule.js +205 -0
- package/dist/op/cron.js +146 -0
- package/dist/op/decide-backend.js +181 -0
- package/dist/op/decide-config.js +68 -0
- package/dist/op/decide-read-inputs.js +141 -0
- package/dist/op/discover.d.ts.map +1 -1
- package/dist/op/discover.js +213 -0
- package/dist/op/duration.js +21 -0
- package/dist/op/emulator-freshness.js +80 -0
- package/dist/op/emulator-lifecycle.js +269 -0
- package/dist/op/factory.js +83 -0
- package/dist/op/gate-approval.js +172 -0
- package/dist/op/gate-name.js +86 -0
- package/dist/op/gate-plan-context.js +167 -0
- package/dist/op/gate-point-run.js +67 -0
- package/dist/op/gate-point.js +111 -0
- package/dist/op/gate-resume-plan.js +155 -0
- package/dist/op/gate-resume.js +31 -0
- package/dist/op/gate-review.js +290 -0
- package/dist/op/gate-run-locator.js +85 -0
- package/dist/op/gate-summary.js +100 -0
- package/dist/op/gate-wait.js +56 -0
- package/dist/op/gate.js +449 -0
- package/dist/op/generate-pipeline.js +124 -0
- package/dist/op/index.js +51 -0
- package/dist/op/lifecycle-receipt-store.js +45 -0
- package/dist/op/local-executor.js +1164 -0
- package/dist/op/local-output.js +140 -0
- package/dist/op/op-ir.js +317 -0
- package/dist/op/op-verb-class.js +131 -0
- package/dist/op/op-waves-notify.js +136 -0
- package/dist/op/op-waves-run.d.ts +1 -1
- package/dist/op/op-waves-run.d.ts.map +1 -1
- package/dist/op/op-waves-run.js +1657 -0
- package/dist/op/op-waves.js +409 -0
- package/dist/op/operator.js +743 -0
- package/dist/op/receipt-store.js +94 -0
- package/dist/op/resource.js +23 -0
- package/dist/op/run-context.js +28 -0
- package/dist/op/run-live.js +256 -0
- package/dist/op/runtime.js +21 -0
- package/dist/op/runtimes/local.js +195 -0
- package/dist/op/source-archive.js +131 -0
- package/dist/op/step-output-ref.js +397 -0
- package/dist/op/steward-beside.js +169 -0
- package/dist/op/steward-points.js +138 -0
- package/dist/op/steward-turn.js +59 -0
- package/dist/op/steward.js +317 -0
- package/dist/op/types.js +25 -0
- package/dist/op/work-lease-decl.js +81 -0
- package/dist/op/work-lease-run.js +393 -0
- package/dist/override-gate.js +284 -0
- package/dist/owner-chain.js +74 -0
- package/dist/ownership.js +92 -0
- package/dist/params.js +61 -0
- package/dist/plan-summary.js +711 -0
- package/dist/point-decision.js +211 -0
- package/dist/pr-forge.js +810 -0
- package/dist/pr-loop.js +203 -0
- package/dist/project-codegen.js +191 -0
- package/dist/project-root.js +74 -0
- package/dist/project-validation.js +50 -0
- package/dist/provenance.js +157 -0
- package/dist/pseudo-parameter.js +40 -0
- package/dist/reconcile.js +450 -0
- package/dist/resource-attributes.js +33 -0
- package/dist/runtime-adapter.js +77 -0
- package/dist/runtime.js +66 -0
- package/dist/secret-materialization.js +153 -0
- package/dist/secret-provenance.js +250 -0
- package/dist/serializer-walker.js +112 -0
- package/dist/serializer.js +1 -0
- package/dist/sort.js +45 -0
- package/dist/stack-output.js +115 -0
- package/dist/telemetry-attribution.js +149 -0
- package/dist/terraform/__fixtures__/build-graph.js +63 -0
- package/dist/terraform/adopt-state.js +40 -0
- package/dist/terraform/aws-resources.js +384 -0
- package/dist/terraform/bridge.js +204 -0
- package/dist/terraform/carve-provider.js +155 -0
- package/dist/terraform/carve.js +138 -0
- package/dist/terraform/data-source-shape.js +57 -0
- package/dist/terraform/emit-source.js +36 -0
- package/dist/terraform/excise.js +123 -0
- package/dist/terraform/graduate.js +128 -0
- package/dist/terraform/graph.js +435 -0
- package/dist/terraform/hcl2json.js +60 -0
- package/dist/terraform/manifest.js +149 -0
- package/dist/terraform/parse.js +81 -0
- package/dist/terraform/providers/aws.js +146 -0
- package/dist/terraform/providers/gcp.js +294 -0
- package/dist/terraform/providers/index.js +17 -0
- package/dist/terraform/providers/kubernetes.js +253 -0
- package/dist/terraform/score.js +151 -0
- package/dist/terraform/state.js +67 -0
- package/dist/terraform/tier-map.js +74 -0
- package/dist/terraform/types.js +13 -0
- package/dist/terraform/unified-diff.js +132 -0
- package/dist/testing.js +295 -0
- package/dist/toml-emit.js +153 -0
- package/dist/toml-parse.js +355 -0
- package/dist/toml-utils.js +60 -0
- package/dist/toml.js +428 -0
- package/dist/ts-loader.d.ts +46 -0
- package/dist/ts-loader.d.ts.map +1 -0
- package/dist/ts-loader.js +103 -0
- package/dist/types.js +1 -0
- package/dist/utils.js +61 -0
- package/dist/validation.js +20 -0
- package/dist/vendor/js-yaml-load.js +1662 -0
- package/dist/workspace/__fixtures__/contract-repo.js +103 -0
- package/dist/workspace/__fixtures__/decide-workspace.js +112 -0
- package/dist/workspace/__fixtures__/sessions.js +91 -0
- package/dist/workspace/__fixtures__/two-member-workspace.js +34 -0
- package/dist/workspace/agent-cli.js +99 -0
- package/dist/workspace/agent.schema.json +390 -0
- package/dist/workspace/answers-ledger.js +89 -0
- package/dist/workspace/box-cli.js +142 -0
- package/dist/workspace/box-factory-write.js +196 -0
- package/dist/workspace/box-factory-write.schema.json +342 -0
- package/dist/workspace/box-factory.js +88 -0
- package/dist/workspace/box-intent.js +126 -0
- package/dist/workspace/box-isolation.js +169 -0
- package/dist/workspace/box-listing-write.schema.json +204 -0
- package/dist/workspace/box-listing.js +298 -0
- package/dist/workspace/box-publish.js +352 -0
- package/dist/workspace/box-publish.schema.json +498 -0
- package/dist/workspace/box-services.js +39 -0
- package/dist/workspace/broker-protocol.js +359 -0
- package/dist/workspace/broker-protocol.schema.json +228 -0
- package/dist/workspace/change-set.schema.json +460 -0
- package/dist/workspace/changes-cli.js +56 -0
- package/dist/workspace/changes.js +446 -0
- package/dist/workspace/changes.schema.json +642 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-fly-site.js +198 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-live-names.js +155 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-rollback.js +214 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.js +180 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-policy.js +99 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit.js +847 -0
- package/dist/workspace/chant-migrations.js +113 -0
- package/dist/workspace/check.schema.json +433 -0
- package/dist/workspace/checks/box-isolation.js +62 -0
- package/dist/workspace/checks/boxes.js +491 -0
- package/dist/workspace/checks/diagrams.js +104 -0
- package/dist/workspace/checks/generated.js +419 -0
- package/dist/workspace/checks/ledgers.js +212 -0
- package/dist/workspace/checks/links.js +191 -0
- package/dist/workspace/checks/live.js +100 -0
- package/dist/workspace/checks/pipelines.js +188 -0
- package/dist/workspace/checks/records.js +169 -0
- package/dist/workspace/checks.js +426 -0
- package/dist/workspace/ci-green-cli.js +125 -0
- package/dist/workspace/ci-green-forge.js +83 -0
- package/dist/workspace/ci-green-workflow.js +199 -0
- package/dist/workspace/ci-green.js +325 -0
- package/dist/workspace/ci-last-green.schema.json +158 -0
- package/dist/workspace/compose-graph.js +235 -0
- package/dist/workspace/compose-reports.js +146 -0
- package/dist/workspace/composites.js +264 -0
- package/dist/workspace/composites.schema.json +617 -0
- package/dist/workspace/conformance/__fixture__/app/package.json +7 -0
- package/dist/workspace/conformance/__fixture__/app/src/server.mjs +29 -0
- package/dist/workspace/conformance/__fixture__/decisions/decision.kind.mjs +54 -0
- package/dist/workspace/conformance/__fixture__/decisions/decision.schema.json +512 -0
- package/dist/workspace/conformance/__fixture__/decisions/fix-001-how-the-app-is-deployed.md +40 -0
- package/dist/workspace/conformance/__fixture__/delivery/chant.config.ts +7 -0
- package/dist/workspace/conformance/__fixture__/delivery/lexicon/index.ts +26 -0
- package/dist/workspace/conformance/__fixture__/delivery/package.json +7 -0
- package/dist/workspace/conformance/__fixture__/delivery/src/app.component.ts +14 -0
- package/dist/workspace/conformance/__fixture__/delivery/src/app.ts +4 -0
- package/dist/workspace/conformance/__fixtures__/reference-broker.js +205 -0
- package/dist/workspace/conformance/__writer_fixture__/answers/answer.kind.mjs +32 -0
- package/dist/workspace/conformance/__writer_fixture__/answers/answer.schema.json +135 -0
- package/dist/workspace/conformance/__writer_fixture__/contracts/contract.kind.mjs +37 -0
- package/dist/workspace/conformance/__writer_fixture__/contracts/contract.schema.json +213 -0
- package/dist/workspace/conformance/__writer_fixture__/decisions/points.json +228 -0
- package/dist/workspace/conformance/__writer_fixture__/drivers/driver.kind.mjs +15 -0
- package/dist/workspace/conformance/__writer_fixture__/drivers/driver.schema.json +38 -0
- package/dist/workspace/conformance/__writer_fixture__/sessions/session.kind.mjs +54 -0
- package/dist/workspace/conformance/__writer_fixture__/sessions/session.schema.json +289 -0
- package/dist/workspace/conformance/__writer_fixture__/work/W-001-the-writer-suite-works-this-item.md +25 -0
- package/dist/workspace/conformance/__writer_fixture__/work/work.kind.mjs +60 -0
- package/dist/workspace/conformance/__writer_fixture__/work/work.schema.json +603 -0
- package/dist/workspace/conformance/broker.js +736 -0
- package/dist/workspace/conformance/index.js +673 -0
- package/dist/workspace/conformance/vitest.js +108 -0
- package/dist/workspace/conformance/writer.js +1080 -0
- package/dist/workspace/decide.js +879 -0
- package/dist/workspace/decision-points.schema.json +135 -135
- package/dist/workspace/declaration-write-cli.js +74 -0
- package/dist/workspace/declaration-write.js +238 -0
- package/dist/workspace/declaration.js +938 -0
- package/dist/workspace/declaration.schema.json +1413 -1413
- package/dist/workspace/declared-kinds.js +106 -0
- package/dist/workspace/environments.js +164 -0
- package/dist/workspace/evidence.schema.json +279 -0
- package/dist/workspace/export-cli.js +185 -0
- package/dist/workspace/export.js +499 -0
- package/dist/workspace/factory-rules.js +175 -0
- package/dist/workspace/generated-files.js +92 -0
- package/dist/workspace/glob.js +45 -0
- package/dist/workspace/graph-cache.js +352 -0
- package/dist/workspace/graph-cli.js +348 -0
- package/dist/workspace/graph.schema.json +1338 -0
- package/dist/workspace/identity-gate-rule.d.ts +65 -0
- package/dist/workspace/identity-gate-rule.d.ts.map +1 -0
- package/dist/workspace/identity-gate-rule.js +75 -0
- package/dist/workspace/identity.d.ts +4 -2
- package/dist/workspace/identity.d.ts.map +1 -1
- package/dist/workspace/identity.js +126 -0
- package/dist/workspace/import.js +283 -0
- package/dist/workspace/init.js +620 -0
- package/dist/workspace/intent-cli.js +263 -0
- package/dist/workspace/intent-joins.js +201 -0
- package/dist/workspace/intent-record.js +352 -0
- package/dist/workspace/intent-record.schema.json +864 -0
- package/dist/workspace/intent.js +1582 -0
- package/dist/workspace/intent.schema.json +2420 -0
- package/dist/workspace/json-edit.js +221 -0
- package/dist/workspace/jsonc.js +235 -0
- package/dist/workspace/kind-readers.js +103 -0
- package/dist/workspace/kinds.js +587 -0
- package/dist/workspace/lexicon-symbols.js +112 -0
- package/dist/workspace/lineage-adopt-cli.js +102 -0
- package/dist/workspace/lineage-adopt.js +360 -0
- package/dist/workspace/lineage-check.js +282 -0
- package/dist/workspace/lineage-cli.js +121 -0
- package/dist/workspace/lineage-hash-index.js +284 -0
- package/dist/workspace/lineage-init.js +507 -0
- package/dist/workspace/lineage-lock.js +387 -0
- package/dist/workspace/lineage-merge.js +45 -0
- package/dist/workspace/lineage-migrations.d.ts.map +1 -1
- package/dist/workspace/lineage-migrations.js +329 -0
- package/dist/workspace/lineage-provenance.js +36 -0
- package/dist/workspace/lineage-update.js +138 -0
- package/dist/workspace/lineage-upgrade-cli.js +131 -0
- package/dist/workspace/lineage-upgrade.js +889 -0
- package/dist/workspace/lineage-vendor.js +171 -0
- package/dist/workspace/lineage-version.js +128 -0
- package/dist/workspace/lineage-versions.js +275 -0
- package/dist/workspace/links.js +330 -0
- package/dist/workspace/ls-generated.js +95 -0
- package/dist/workspace/ls.js +277 -0
- package/dist/workspace/ls.schema.json +333 -0
- package/dist/workspace/member-commands.js +508 -0
- package/dist/workspace/member-handles.js +216 -0
- package/dist/workspace/member-pipeline.js +258 -0
- package/dist/workspace/member-run.js +117 -0
- package/dist/workspace/member-write.schema.json +193 -0
- package/dist/workspace/nested-graph.js +156 -0
- package/dist/workspace/nesting.js +38 -0
- package/dist/workspace/patch.js +301 -0
- package/dist/workspace/patch.schema.json +321 -0
- package/dist/workspace/pin-cli.js +55 -0
- package/dist/workspace/pin-integrity.js +100 -0
- package/dist/workspace/plan-summary.schema.json +155 -0
- package/dist/workspace/point-answer.schema.json +129 -129
- package/dist/workspace/points-cli.js +291 -0
- package/dist/workspace/points-write.schema.json +660 -0
- package/dist/workspace/points.js +521 -0
- package/dist/workspace/points.schema.json +916 -0
- package/dist/workspace/pr-report.schema.json +108 -0
- package/dist/workspace/principal-classes.d.ts +3 -14
- package/dist/workspace/principal-classes.d.ts.map +1 -1
- package/dist/workspace/principal-classes.js +245 -0
- package/dist/workspace/reason-codes.js +326 -0
- package/dist/workspace/record-assets.js +151 -0
- package/dist/workspace/record-decided.js +105 -0
- package/dist/workspace/record-sessions.js +96 -0
- package/dist/workspace/record-source.js +114 -0
- package/dist/workspace/records-amend.schema.json +284 -0
- package/dist/workspace/records-checkout.js +76 -0
- package/dist/workspace/records-cli.js +707 -0
- package/dist/workspace/records-close.js +116 -0
- package/dist/workspace/records-close.schema.json +278 -0
- package/dist/workspace/records-new.schema.json +202 -0
- package/dist/workspace/records-review.schema.json +367 -0
- package/dist/workspace/records-since.js +317 -0
- package/dist/workspace/records-since.schema.json +218 -0
- package/dist/workspace/records-write.js +1229 -0
- package/dist/workspace/records.js +1345 -0
- package/dist/workspace/records.schema.json +604 -0
- package/dist/workspace/reference-kinds/app/package.json +10 -0
- package/dist/workspace/reference-kinds/app/workspace-kinds.json +45 -0
- package/dist/workspace/returns.js +303 -0
- package/dist/workspace/root-refusal.js +75 -0
- package/dist/workspace/run-statement.schema.json +622 -0
- package/dist/workspace/runs-cli.js +584 -0
- package/dist/workspace/runs-write.schema.json +994 -0
- package/dist/workspace/runs.js +708 -0
- package/dist/workspace/runs.schema.json +1257 -0
- package/dist/workspace/runtimes.js +76 -0
- package/dist/workspace/session-kinds.js +83 -0
- package/dist/workspace/signers.schema.json +206 -0
- package/dist/workspace/source-block.js +125 -0
- package/dist/workspace/squash.js +177 -0
- package/dist/workspace/status-gates.js +162 -0
- package/dist/workspace/status-stewards.js +330 -0
- package/dist/workspace/status.js +514 -0
- package/dist/workspace/status.schema.json +998 -0
- package/dist/workspace/steward-index.js +191 -0
- package/dist/workspace/symbols.js +188 -0
- package/dist/workspace/template-manifest.js +227 -0
- package/dist/workspace/template-pins.js +115 -0
- package/dist/workspace/trailer-joins.js +65 -0
- package/dist/workspace/trailers.js +111 -0
- package/dist/workspace/tree.js +136 -0
- package/dist/workspace/trust/attestor.js +63 -0
- package/dist/workspace/trust/dsse.js +108 -0
- package/dist/workspace/trust/evidence-cli.js +175 -0
- package/dist/workspace/trust/evidence.js +161 -0
- package/dist/workspace/trust/gate-seal.js +226 -0
- package/dist/workspace/trust/policy.js +190 -0
- package/dist/workspace/trust/provenance.js +180 -0
- package/dist/workspace/trust/rotation.js +298 -0
- package/dist/workspace/trust/run-commits.js +101 -0
- package/dist/workspace/trust/run-statement.js +182 -0
- package/dist/workspace/trust/seal.js +272 -0
- package/dist/workspace/trust/signers-cli.js +152 -0
- package/dist/workspace/trust/ssh-commit.js +178 -0
- package/dist/workspace/trust/test-repo.js +118 -0
- package/dist/workspace/trust/trust-files.js +193 -0
- package/dist/workspace/trust/verify-cli.js +63 -0
- package/dist/workspace/trust/verify.js +192 -0
- package/dist/workspace/which-chant.js +141 -0
- package/dist/workspace/wip-cli.js +95 -0
- package/dist/workspace/wip-write.schema.json +504 -0
- package/dist/workspace/wip.js +631 -0
- package/dist/workspace/wip.schema.json +334 -0
- package/dist/workspace/work-cli.js +448 -0
- package/dist/workspace/work-evidence.js +122 -0
- package/dist/workspace/work-evidence.schema.json +222 -0
- package/dist/workspace/work-history.schema.json +421 -0
- package/dist/workspace/work-lease.schema.json +246 -0
- package/dist/workspace/work-size.js +77 -0
- package/dist/workspace/work.js +283 -0
- package/dist/workspace/workspace-kinds.schema.json +259 -259
- package/dist/workspace/workspace-principals.schema.json +53 -53
- package/dist/workspace/write-lock-cli.js +105 -0
- package/dist/workspace/write-lock.js +424 -0
- package/dist/workspace/write-lock.schema.json +172 -0
- package/dist/workspace/write-scope.js +484 -0
- package/dist/yaml.js +459 -0
- package/package.json +2 -2
- package/src/cli/commands/check-lexicon-plugin.ts +2 -0
- package/src/cli/commands/lint.ts +36 -6
- package/src/cli/commands/migrate.ts +2 -0
- package/src/cli/compiled-cli.e2e.test.ts +165 -0
- package/src/cli/main.ts +121 -27
- package/src/cli/mcp/workspace-tools.ts +1 -1
- package/src/codegen/pinned-upgrade.ts +2 -0
- package/src/components/discover.ts +2 -0
- package/src/components/sandbox/driver.ts +3 -1
- package/src/config-import.ts +3 -0
- package/src/discovery/fold-import.ts +23 -14
- package/src/discovery/import.ts +2 -0
- package/src/discovery/sandbox/driver.ts +3 -1
- package/src/gate-decision-bundle.test.ts +3 -2
- package/src/gate-decision-identity.test.ts +176 -0
- package/src/gate-decision.ts +100 -22
- package/src/lexicon-module.ts +3 -0
- package/src/lifecycle/gate-ledger.ts +8 -0
- package/src/lint/config.ts +1 -1
- package/src/lint/discover.ts +28 -8
- package/src/lint/policy-import.ts +4 -1
- package/src/lint/rule-loader.ts +2 -0
- package/src/op/discover.ts +3 -0
- package/src/op/op-waves-point.test.ts +1 -0
- package/src/op/op-waves-run.ts +5 -4
- package/src/op/op-waves.test.ts +14 -0
- package/src/ts-loader.test.ts +23 -0
- package/src/ts-loader.ts +122 -0
- package/src/workspace/chant-migrations/chud-lexicon-exit.ts +1 -1
- package/src/workspace/declaration.ts +1 -1
- package/src/workspace/identity-gate-rule.ts +109 -0
- package/src/workspace/identity.ts +8 -18
- package/src/workspace/kinds.ts +1 -1
- package/src/workspace/lineage-migrations.ts +2 -0
- package/src/workspace/points.ts +1 -1
- package/src/workspace/principal-classes.ts +5 -30
|
@@ -0,0 +1,1162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `chant operator` / `chant operator status` / `chant approve` (#1485,
|
|
3
|
+
* epic #1487) — the CLI surface for the native durable-tick operator. See
|
|
4
|
+
* `../../op/operator.ts` for the loop itself; this module is the thin CLI
|
|
5
|
+
* shell around it (discover, load activities, print), mirroring
|
|
6
|
+
* `run.ts`'s `runOpLocal` shape (SIGINT → AbortController, `loadActivities`/
|
|
7
|
+
* `loadProfiles` from the project's configured lexicons).
|
|
8
|
+
*/
|
|
9
|
+
import { lexiconNames } from "../../lexicon-module.js";
|
|
10
|
+
import { loadChantConfig } from "../../config.js";
|
|
11
|
+
import { build } from "../../build.js";
|
|
12
|
+
import { isObservableDeclarable } from "../../declarable.js";
|
|
13
|
+
import { collectBuildRootContributors, collectChangeSubscribers } from "../plugins.js";
|
|
14
|
+
import { discoverOps, discoverStewards } from "../../op/discover.js";
|
|
15
|
+
import { pickSteward, stewardBesideOf, stewardFormFor, stewardLeaseName, stewardTurnOps, DEFAULT_STEWARD_ENV } from "../../op/steward.js";
|
|
16
|
+
import { loadActivities, loadProfiles } from "../../op/activity-registry.js";
|
|
17
|
+
import { parseDuration } from "../../op/local-executor.js";
|
|
18
|
+
import { discoverConvergeOps, runOperatorRound, runOperatorForever, formatRoundLine, formatSignalLine, DEFAULT_OPERATOR_INTERVAL_MS, acquireStewardLease, createBesideState, waitForBesideRuns, } from "../../op/operator.js";
|
|
19
|
+
import { readLease, releaseLease, currentHolderId, DEFAULT_LEASE_TTL_MS } from "../../lifecycle/lease.js";
|
|
20
|
+
import { readConvergeLedger } from "../../lifecycle/converge-ledger.js";
|
|
21
|
+
import { readRunLedger, runEnvOf } from "../../lifecycle/run-ledger.js";
|
|
22
|
+
import { currentGateOrigin, isModelAuthored, sameOriginRefusal, UNATTESTED_APPROVER, } from "../../lifecycle/gate-origin.js";
|
|
23
|
+
import { appendGateResolution, appendPendingGate, readGateResolutions, readGateLedger, latestResolutionSince, latestPendingGate, isPendingGateExpired, resolveApprovalUrl, isApprovalUrl, } from "../../lifecycle/gate-ledger.js";
|
|
24
|
+
import { isPlanDigest, samePlanDigest } from "../../lifecycle/plan-digest.js";
|
|
25
|
+
import { gateContextDigest, gatePolicyRequest, loadGatePolicyEvaluator, } from "../../op/gate-approval.js";
|
|
26
|
+
import { approverOf, humanQuorumSince, tallyGateApprovals } from "../../op/gate.js";
|
|
27
|
+
import { FAN_OUT_GATE_OP, OVERRIDE_GATE_OP, WORKSPACE_UPGRADE_GATE_OP } from "../../op/gate-name.js";
|
|
28
|
+
import { PR_GATE_OP } from "../../pr-loop.js";
|
|
29
|
+
import { pushLifecycleReport, requireLifecycleLedger } from "../../lifecycle/git.js";
|
|
30
|
+
import { LIFECYCLE_LOCAL_NOTE, isLifecycleLocalNote } from "../../lifecycle/local-note.js";
|
|
31
|
+
import { formatError, formatWarning, formatSuccess, formatBold, formatInfo } from "../format.js";
|
|
32
|
+
/** Load the activities a local tick needs, the same way a local run does: core's base activities plus whatever the project's configured lexicons contribute. Best-effort on the lexicon list — an unreadable `chant.config.ts` falls back to base activities only. */
|
|
33
|
+
async function loadOperatorActivities() {
|
|
34
|
+
let lexicons = [];
|
|
35
|
+
try {
|
|
36
|
+
lexicons = lexiconNames((await loadChantConfig(process.cwd())).config.lexicons ?? []);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// No/invalid chant.config — fall back to base activities only.
|
|
40
|
+
}
|
|
41
|
+
return Promise.all([loadActivities(lexicons), loadProfiles()]);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Bind the change-signal seams (#1981) the daemon will keep subscribed, or an
|
|
45
|
+
* empty list.
|
|
46
|
+
*
|
|
47
|
+
* Three gates, in cost order, so a project that gains nothing from this pays
|
|
48
|
+
* nothing for it:
|
|
49
|
+
*
|
|
50
|
+
* 1. No configured lexicon implements `subscribeChanges`. Return immediately,
|
|
51
|
+
* without loading config or building anything. This is every project today.
|
|
52
|
+
* 2. No `--env`. A subscription resolves the same cluster binding a read
|
|
53
|
+
* does, and there is no binding to resolve without an environment. Said out
|
|
54
|
+
* loud rather than silently skipped, because "why did it not wake" is
|
|
55
|
+
* otherwise unanswerable.
|
|
56
|
+
* 3. The build that supplies the declared entities failed. Warn and fall back
|
|
57
|
+
* to the timer. A subscription is an optimization; a build error here must
|
|
58
|
+
* not stop the operator, which has its own per-tick build inside the tick.
|
|
59
|
+
*/
|
|
60
|
+
async function collectOperatorSubscribers(ctx, env) {
|
|
61
|
+
if (!ctx.plugins.some((p) => typeof p.subscribeChanges === "function"))
|
|
62
|
+
return [];
|
|
63
|
+
if (!env) {
|
|
64
|
+
console.error(formatWarning({
|
|
65
|
+
message: "a change signal needs an environment to resolve its binding, so this runs on the timer alone",
|
|
66
|
+
hint: "pass --env <env> to let a lexicon's subscribeChanges wake a tick early",
|
|
67
|
+
}));
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
const cwd = process.cwd();
|
|
71
|
+
try {
|
|
72
|
+
const { config } = await loadChantConfig(cwd);
|
|
73
|
+
const buildRoots = collectBuildRootContributors(ctx.plugins, config, cwd);
|
|
74
|
+
const buildResult = await build(config.sourceDir ?? ".", ctx.serializers, undefined, { buildRoots });
|
|
75
|
+
if (buildResult.errors.length > 0) {
|
|
76
|
+
console.error(formatWarning({
|
|
77
|
+
message: "build failed while scoping the change signal, so this runs on the timer alone",
|
|
78
|
+
}));
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
// The declared estate, sliced per lexicon: the same slice
|
|
82
|
+
// `takeSnapshot` hands `describeResources`, and the bound on what any
|
|
83
|
+
// subscription may watch.
|
|
84
|
+
const entities = new Map();
|
|
85
|
+
for (const [name, entity] of buildResult.entities) {
|
|
86
|
+
if (!isObservableDeclarable(entity))
|
|
87
|
+
continue;
|
|
88
|
+
let perLexicon = entities.get(entity.lexicon);
|
|
89
|
+
if (!perLexicon)
|
|
90
|
+
entities.set(entity.lexicon, (perLexicon = new Map()));
|
|
91
|
+
perLexicon.set(name, {
|
|
92
|
+
entityType: entity.entityType,
|
|
93
|
+
props: (entity.props != null ? entity.props : {}),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return collectChangeSubscribers(ctx.plugins, { environment: env, cwd, entities });
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
console.error(formatWarning({
|
|
100
|
+
message: `could not scope the change signal, so this runs on the timer alone (${err instanceof Error ? err.message : String(err)})`,
|
|
101
|
+
}));
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// ── chant operator ──────────────────────────────────────────────────────────
|
|
106
|
+
/**
|
|
107
|
+
* `chant operator [--env <env>] [--interval <duration>] [--lease-ttl
|
|
108
|
+
* <duration>] [--once]` — run scheduled ticks for this project's discovered
|
|
109
|
+
* ConvergeOps, locally, with no service to install (issue's own worked
|
|
110
|
+
* example). `--once` runs a single round and exits (also the offline test
|
|
111
|
+
* story, and what a cron/systemd-timer/k8s-CronJob invoker uses instead of
|
|
112
|
+
* leaving the daemon running); omitted, the daemon loops until Ctrl-C.
|
|
113
|
+
*/
|
|
114
|
+
export async function runOperator(ctx) {
|
|
115
|
+
if (ctx.args.steward !== undefined)
|
|
116
|
+
return runStewardOperator(ctx);
|
|
117
|
+
const { ops, errors } = await discoverConvergeOps({ env: ctx.args.env });
|
|
118
|
+
for (const err of errors)
|
|
119
|
+
console.error(formatWarning({ message: err }));
|
|
120
|
+
if (ops.length === 0) {
|
|
121
|
+
console.error(formatWarning({
|
|
122
|
+
message: ctx.args.env
|
|
123
|
+
? `No ConvergeOp declarations found for env "${ctx.args.env}"`
|
|
124
|
+
: "No ConvergeOp declarations found (*.op.ts built with ConvergeOp(...))",
|
|
125
|
+
}));
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
128
|
+
let activities, profiles;
|
|
129
|
+
try {
|
|
130
|
+
[activities, profiles] = await loadOperatorActivities();
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
|
|
134
|
+
return 1;
|
|
135
|
+
}
|
|
136
|
+
const intervalMs = ctx.args.interval ? parseDuration(ctx.args.interval) : DEFAULT_OPERATOR_INTERVAL_MS;
|
|
137
|
+
const leaseTtlMs = ctx.args.leaseTtl ? parseDuration(ctx.args.leaseTtl) : DEFAULT_LEASE_TTL_MS;
|
|
138
|
+
const controller = new AbortController();
|
|
139
|
+
// `process.on`, not `.once`: a `.once` listener is gone the instant it
|
|
140
|
+
// fires, so a second SIGINT arriving while shutdown is still in flight
|
|
141
|
+
// (the `finally` below hasn't run yet) has no listener left and takes
|
|
142
|
+
// Node's default disposition — the process ends right there, before
|
|
143
|
+
// whatever the `finally` was going to do. Kept registered for the whole
|
|
144
|
+
// shutdown and only removed once it's actually over; `stopping` makes a
|
|
145
|
+
// repeat signal a no-op rather than a second `abort()`/log line (#2872).
|
|
146
|
+
let stopping = false;
|
|
147
|
+
const onSigint = () => {
|
|
148
|
+
if (stopping)
|
|
149
|
+
return;
|
|
150
|
+
stopping = true;
|
|
151
|
+
console.error(formatWarning({ message: "interrupted — stopping operator" }));
|
|
152
|
+
controller.abort();
|
|
153
|
+
};
|
|
154
|
+
process.on("SIGINT", onSigint);
|
|
155
|
+
const printRound = (events) => {
|
|
156
|
+
for (const event of events)
|
|
157
|
+
console.error(formatInfo(formatRoundLine(event)));
|
|
158
|
+
};
|
|
159
|
+
try {
|
|
160
|
+
if (ctx.args.once) {
|
|
161
|
+
const events = await runOperatorRound({ env: ctx.args.env, leaseTtlMs, activities, profiles, signal: controller.signal });
|
|
162
|
+
printRound(events);
|
|
163
|
+
return events.some((e) => e.kind === "tick-failed") ? 1 : 0;
|
|
164
|
+
}
|
|
165
|
+
console.error(formatInfo(`chant operator: watching ${ops.length} ConvergeOp(s) every ${intervalMs}ms (Ctrl-C to stop)`));
|
|
166
|
+
// A change signal (#1981) only ever shortens the sleep above. Every round
|
|
167
|
+
// it wakes is the round the timer would have run.
|
|
168
|
+
const subscribers = await collectOperatorSubscribers(ctx, ctx.args.env);
|
|
169
|
+
await runOperatorForever({
|
|
170
|
+
env: ctx.args.env,
|
|
171
|
+
intervalMs,
|
|
172
|
+
leaseTtlMs,
|
|
173
|
+
activities,
|
|
174
|
+
profiles,
|
|
175
|
+
signal: controller.signal,
|
|
176
|
+
onRound: printRound,
|
|
177
|
+
subscribers,
|
|
178
|
+
onSignalEvent: (event) => console.error(formatInfo(formatSignalLine(event))),
|
|
179
|
+
});
|
|
180
|
+
return 0;
|
|
181
|
+
}
|
|
182
|
+
finally {
|
|
183
|
+
// Only when no signal started this shutdown. After one did, the process
|
|
184
|
+
// exits right after this returns, and removing the listener would put
|
|
185
|
+
// SIGINT back to its default disposition for that last stretch: a repeat
|
|
186
|
+
// signal arriving there ends the process by signal instead of the exit
|
|
187
|
+
// code (see the steward's `finally` below).
|
|
188
|
+
if (!stopping)
|
|
189
|
+
process.removeListener("SIGINT", onSigint);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// ── chant operator --steward ────────────────────────────────────────────────
|
|
193
|
+
/**
|
|
194
|
+
* `chant operator --steward [<name>] [--env <env>] [--interval] [--lease-ttl]
|
|
195
|
+
* [--once]` (#2731) — a steward's local form. The same Ops the steward runs on
|
|
196
|
+
* Fountain, on the same crons, one at a time, each under its operator lease,
|
|
197
|
+
* and the whole process under the steward's own lease.
|
|
198
|
+
*
|
|
199
|
+
* Refuses, before ticking anything:
|
|
200
|
+
* - an environment whose form is `fountain`, because there the composite's
|
|
201
|
+
* Agent and Teammate are the steward and this process would be a second
|
|
202
|
+
* writer beside them;
|
|
203
|
+
* - a steward whose lease another live process holds (a second local
|
|
204
|
+
* operator for it).
|
|
205
|
+
*/
|
|
206
|
+
export async function runStewardOperator(ctx) {
|
|
207
|
+
const { stewards, errors, conflicts } = await discoverStewards();
|
|
208
|
+
for (const err of errors)
|
|
209
|
+
console.error(formatWarning({ message: err }));
|
|
210
|
+
for (const conflict of conflicts)
|
|
211
|
+
console.error(formatError({ message: conflict }));
|
|
212
|
+
const picked = pickSteward(stewards, ctx.args.steward ?? "");
|
|
213
|
+
if (typeof picked === "string") {
|
|
214
|
+
console.error(formatError({ message: picked }));
|
|
215
|
+
return 1;
|
|
216
|
+
}
|
|
217
|
+
const steward = picked;
|
|
218
|
+
const env = ctx.args.env ?? DEFAULT_STEWARD_ENV;
|
|
219
|
+
const form = stewardFormFor(steward, env);
|
|
220
|
+
if (form !== "local") {
|
|
221
|
+
console.error(formatError({
|
|
222
|
+
message: `steward "${steward.name}" runs on ${form} in environment "${env}", so a local operator for it would be a second writer`,
|
|
223
|
+
hint: `Its turns run on the fountain teammate "${steward.name}". Run this in an environment the declaration's form makes local, or change the form.`,
|
|
224
|
+
}));
|
|
225
|
+
return 1;
|
|
226
|
+
}
|
|
227
|
+
let activities, profiles;
|
|
228
|
+
try {
|
|
229
|
+
[activities, profiles] = await loadOperatorActivities();
|
|
230
|
+
}
|
|
231
|
+
catch (err) {
|
|
232
|
+
console.error(formatError({ message: err instanceof Error ? err.message : String(err) }));
|
|
233
|
+
return 1;
|
|
234
|
+
}
|
|
235
|
+
const intervalMs = ctx.args.interval ? parseDuration(ctx.args.interval) : DEFAULT_OPERATOR_INTERVAL_MS;
|
|
236
|
+
const leaseTtlMs = ctx.args.leaseTtl ? parseDuration(ctx.args.leaseTtl) : DEFAULT_LEASE_TTL_MS;
|
|
237
|
+
const holder = currentHolderId();
|
|
238
|
+
const controller = new AbortController();
|
|
239
|
+
// `process.on`, not `.once` (#2872): a supervisor that signals both a
|
|
240
|
+
// process group and a child sends a second SIGTERM close behind the
|
|
241
|
+
// first. A `.once` listener has already unregistered itself by then, so
|
|
242
|
+
// that second signal takes Node's default disposition and ends the
|
|
243
|
+
// process immediately — before the `finally` below releases the
|
|
244
|
+
// steward's lease. Kept registered for the whole shutdown and removed
|
|
245
|
+
// only once it's over, so a second, third or later signal is a no-op
|
|
246
|
+
// (`stopping` guards against re-aborting or re-logging) rather than a
|
|
247
|
+
// fast exit that skips the release.
|
|
248
|
+
//
|
|
249
|
+
// Registered before the steward's lease is taken, not after: the lease is
|
|
250
|
+
// visible to other readers as soon as its ref is written, and the acquire
|
|
251
|
+
// goes on to push it. A SIGTERM in that stretch found no listener and ended
|
|
252
|
+
// the process by signal with the lease still held (the
|
|
253
|
+
// operator-steward-signal e2e test failed this way). Caught here, it aborts
|
|
254
|
+
// the controller, the loop below starts no round, and the `finally`
|
|
255
|
+
// releases the lease.
|
|
256
|
+
let stopping = false;
|
|
257
|
+
const onSigint = () => {
|
|
258
|
+
if (stopping)
|
|
259
|
+
return;
|
|
260
|
+
stopping = true;
|
|
261
|
+
console.error(formatWarning({ message: "interrupted — stopping steward" }));
|
|
262
|
+
controller.abort();
|
|
263
|
+
};
|
|
264
|
+
process.on("SIGINT", onSigint);
|
|
265
|
+
process.on("SIGTERM", onSigint);
|
|
266
|
+
// Refuse a second local operator up front rather than letting it loop on
|
|
267
|
+
// `steward-busy` rounds: one steward is one writer.
|
|
268
|
+
const first = await acquireStewardLease(steward.name, holder, { ttlMs: leaseTtlMs }).catch((err) => {
|
|
269
|
+
process.removeListener("SIGINT", onSigint);
|
|
270
|
+
process.removeListener("SIGTERM", onSigint);
|
|
271
|
+
throw err;
|
|
272
|
+
});
|
|
273
|
+
if (!first.acquired) {
|
|
274
|
+
console.error(formatError({
|
|
275
|
+
message: `steward "${steward.name}" is already running (lease held by ${first.heldBy?.holder ?? "another process"} until ${first.heldBy?.expiresAt ?? "?"})`,
|
|
276
|
+
hint: "One steward is one writer. Stop the other operator, or wait for its lease to expire.",
|
|
277
|
+
}));
|
|
278
|
+
process.removeListener("SIGINT", onSigint);
|
|
279
|
+
process.removeListener("SIGTERM", onSigint);
|
|
280
|
+
return 1;
|
|
281
|
+
}
|
|
282
|
+
const printRound = (events) => {
|
|
283
|
+
for (const event of events)
|
|
284
|
+
console.error(formatInfo(formatRoundLine(event)));
|
|
285
|
+
};
|
|
286
|
+
const turnOps = stewardTurnOps(steward);
|
|
287
|
+
const scheduled = turnOps.filter((op) => op.schedule !== undefined);
|
|
288
|
+
const beside = stewardBesideOf(steward).map((b) => b.op);
|
|
289
|
+
try {
|
|
290
|
+
if (ctx.args.once) {
|
|
291
|
+
// One round, and the runs it started beside the turns (#2861) to their end.
|
|
292
|
+
const besideState = createBesideState();
|
|
293
|
+
const events = await runOperatorRound({ steward, holder, leaseTtlMs, activities, profiles, signal: controller.signal, besideState, stewardEnv: env });
|
|
294
|
+
printRound(events);
|
|
295
|
+
await waitForBesideRuns(besideState);
|
|
296
|
+
const ended = besideState.ended.splice(0);
|
|
297
|
+
printRound(ended);
|
|
298
|
+
return events.some((e) => e.kind === "tick-failed" || e.kind === "steward-busy" || e.kind === "ready-failed") ||
|
|
299
|
+
ended.some((e) => e.kind === "beside-ended" && e.code !== 0 && e.code !== 3)
|
|
300
|
+
? 1
|
|
301
|
+
: 0;
|
|
302
|
+
}
|
|
303
|
+
console.error(formatInfo(`chant operator: steward ${steward.name} (local) runs ${scheduled.length} scheduled Op(s) of ${turnOps.length} as its turns` +
|
|
304
|
+
(beside.length > 0 ? `, and ${beside.join(", ")} beside them` : "") +
|
|
305
|
+
`, checking every ${intervalMs}ms (Ctrl-C to stop)`));
|
|
306
|
+
await runOperatorForever({
|
|
307
|
+
steward,
|
|
308
|
+
stewardEnv: env,
|
|
309
|
+
holder,
|
|
310
|
+
intervalMs,
|
|
311
|
+
leaseTtlMs,
|
|
312
|
+
activities,
|
|
313
|
+
profiles,
|
|
314
|
+
signal: controller.signal,
|
|
315
|
+
onRound: printRound,
|
|
316
|
+
});
|
|
317
|
+
return 0;
|
|
318
|
+
}
|
|
319
|
+
finally {
|
|
320
|
+
// The listeners stay registered through this whole `finally`, not just
|
|
321
|
+
// the `try` above (#2872): the lease release below is itself async (two
|
|
322
|
+
// more git plumbing calls), and a repeat signal landing in that window
|
|
323
|
+
// is exactly the shape the issue reported — removing the listeners
|
|
324
|
+
// first, before awaiting the release, would reopen the same hole one
|
|
325
|
+
// step later. `stopping` (set already) makes every repeat here a no-op.
|
|
326
|
+
// A courtesy: the lease expires on its own if this never runs.
|
|
327
|
+
const { record } = await readLease(stewardLeaseName(steward.name)).catch(() => ({ record: undefined }));
|
|
328
|
+
if (record?.holder === holder)
|
|
329
|
+
await releaseLease(stewardLeaseName(steward.name), holder, record.token).catch(() => false);
|
|
330
|
+
// Removed only when no signal started this shutdown. After one did, the
|
|
331
|
+
// CLI goes on to `flushAndExit` (../main.ts) and `process.exit`, and
|
|
332
|
+
// removing the last SIGTERM listener hands the signal back to its
|
|
333
|
+
// default disposition for that stretch: a repeat SIGTERM arriving there
|
|
334
|
+
// ended the process by signal (exit code null) with the lease already
|
|
335
|
+
// released. That was the operator-steward-signal e2e test's intermittent
|
|
336
|
+
// failure; a SIGTERM every 2ms after the first hit it 8 runs of 8.
|
|
337
|
+
if (!stopping) {
|
|
338
|
+
process.removeListener("SIGINT", onSigint);
|
|
339
|
+
process.removeListener("SIGTERM", onSigint);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Every gate an op is currently standing at, read from its own gate ledger
|
|
345
|
+
* (#2119) — a live (unexpired) pending fact with no resolution newer than it.
|
|
346
|
+
*
|
|
347
|
+
* Ledger-first, so `chant operator status` sees a gate any run recorded, not
|
|
348
|
+
* only one a converge tick's dispatch reached. Before this, a plain `chant run
|
|
349
|
+
* <op>` could not produce a pending gate at all (the executor refused the op
|
|
350
|
+
* outright), so reading the converge ledger's `gated` outcomes was the whole
|
|
351
|
+
* story; now it is one source among the ops' own.
|
|
352
|
+
*/
|
|
353
|
+
export async function pendingGatesFor(opName, opts = {}) {
|
|
354
|
+
const now = opts.now ?? new Date().toISOString();
|
|
355
|
+
const { resolutions, pending } = await readGateLedger(opName, { cwd: opts.cwd });
|
|
356
|
+
const standing = [];
|
|
357
|
+
for (const gate of new Set(pending.map((p) => p.gate))) {
|
|
358
|
+
const latest = latestPendingGate(pending, gate);
|
|
359
|
+
if (!latest || isPendingGateExpired(latest, now))
|
|
360
|
+
continue;
|
|
361
|
+
if (latestResolutionSince(resolutions, gate, latest.timestamp))
|
|
362
|
+
continue;
|
|
363
|
+
standing.push(latest);
|
|
364
|
+
}
|
|
365
|
+
return standing.sort((a, b) => a.gate.localeCompare(b.gate));
|
|
366
|
+
}
|
|
367
|
+
async function statusFor(opName, env, cwd) {
|
|
368
|
+
const [{ records }, { record: lease }] = await Promise.all([
|
|
369
|
+
readConvergeLedger(env, { cwd }),
|
|
370
|
+
readLease(opName, { cwd }),
|
|
371
|
+
]);
|
|
372
|
+
const ownRecords = records.filter((r) => r.op === opName);
|
|
373
|
+
const lastTick = ownRecords.at(-1);
|
|
374
|
+
const pendingGates = [];
|
|
375
|
+
const seen = new Set();
|
|
376
|
+
// The dispatched ops this ConvergeOp's last tick recorded as gated, plus the
|
|
377
|
+
// ConvergeOp itself — a gate can sit on either.
|
|
378
|
+
const gateOps = new Set([opName]);
|
|
379
|
+
const ruleFor = new Map();
|
|
380
|
+
for (const outcome of lastTick?.outcomes ?? []) {
|
|
381
|
+
if (outcome.action !== "gated" || !outcome.gateName || !outcome.op)
|
|
382
|
+
continue;
|
|
383
|
+
gateOps.add(outcome.op);
|
|
384
|
+
ruleFor.set(`${outcome.op}\0${outcome.gateName}`, outcome.ruleId);
|
|
385
|
+
}
|
|
386
|
+
for (const gateOp of [...gateOps].sort()) {
|
|
387
|
+
for (const record of await pendingGatesFor(gateOp, { cwd })) {
|
|
388
|
+
seen.add(`${gateOp}\0${record.gate}`);
|
|
389
|
+
pendingGates.push({
|
|
390
|
+
op: gateOp,
|
|
391
|
+
gate: record.gate,
|
|
392
|
+
expiresAt: record.expiresAt,
|
|
393
|
+
...(ruleFor.get(`${gateOp}\0${record.gate}`) ? { rule: ruleFor.get(`${gateOp}\0${record.gate}`) } : {}),
|
|
394
|
+
...(record.description ? { description: record.description } : {}),
|
|
395
|
+
...(record.url ? { url: record.url } : {}),
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
// A tick that recorded a `gated` outcome before the executor started writing
|
|
400
|
+
// pending facts (#2119) still has nothing in `_gates` to read; fall back to
|
|
401
|
+
// the outcome itself so an in-flight gate isn't dropped on upgrade.
|
|
402
|
+
if (lastTick) {
|
|
403
|
+
for (const outcome of lastTick.outcomes) {
|
|
404
|
+
if (outcome.action !== "gated" || !outcome.gateName || !outcome.op)
|
|
405
|
+
continue;
|
|
406
|
+
if (seen.has(`${outcome.op}\0${outcome.gateName}`))
|
|
407
|
+
continue;
|
|
408
|
+
const { records: resolutions } = await readGateResolutions(outcome.op, { cwd });
|
|
409
|
+
if (latestResolutionSince(resolutions, outcome.gateName, lastTick.timestamp))
|
|
410
|
+
continue;
|
|
411
|
+
pendingGates.push({
|
|
412
|
+
rule: outcome.ruleId,
|
|
413
|
+
op: outcome.op,
|
|
414
|
+
gate: outcome.gateName,
|
|
415
|
+
...(outcome.url ? { url: outcome.url } : {}),
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return {
|
|
420
|
+
op: opName,
|
|
421
|
+
env,
|
|
422
|
+
lastTick,
|
|
423
|
+
pendingGates,
|
|
424
|
+
lease: lease ? { holder: lease.holder, expiresAt: lease.expiresAt } : undefined,
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* `chant operator status [--env <env>] [--json]` — last tick, outcome
|
|
429
|
+
* counts, and pending gates, read from the `chant/lifecycle` orphan branch
|
|
430
|
+
* alone (issue acceptance criterion). No live query, no daemon needs to be
|
|
431
|
+
* running: everything here was already durably recorded by whichever
|
|
432
|
+
* process (this operator, a bare `chant run <op>`, or another machine's
|
|
433
|
+
* operator) ran the last tick.
|
|
434
|
+
*
|
|
435
|
+
* Pending gates are read from every discovered op's own gate ledger, not only
|
|
436
|
+
* from a converge tick's `gated` outcomes (#2119) — a gate an ordinary `chant
|
|
437
|
+
* run <op>` stopped at is the same standing fact and shows up the same way,
|
|
438
|
+
* under "pending gates" for ops with no converge tick behind them.
|
|
439
|
+
*/
|
|
440
|
+
export async function runOperatorStatus(ctx) {
|
|
441
|
+
const { ops, errors } = await discoverConvergeOps({ env: ctx.args.env });
|
|
442
|
+
for (const err of errors)
|
|
443
|
+
console.error(formatWarning({ message: err }));
|
|
444
|
+
const rows = await Promise.all(ops.map((d) => statusFor(d.config.name, d.config.labels?.Env ?? "unknown")));
|
|
445
|
+
// Every other discovered op that is standing at a gate. `discoverOps` sees
|
|
446
|
+
// the whole project, so an ApplyOp someone ran by hand this morning is here
|
|
447
|
+
// even though no ConvergeOp ever dispatched it.
|
|
448
|
+
const covered = new Set(rows.flatMap((r) => r.pendingGates.map((g) => `${g.op ?? r.op} ${g.gate}`)));
|
|
449
|
+
const { ops: allOps } = await discoverOps();
|
|
450
|
+
const standalone = [];
|
|
451
|
+
for (const opName of [...allOps.keys()].sort()) {
|
|
452
|
+
for (const record of await pendingGatesFor(opName)) {
|
|
453
|
+
if (covered.has(`${opName} ${record.gate}`))
|
|
454
|
+
continue;
|
|
455
|
+
standalone.push({
|
|
456
|
+
op: opName,
|
|
457
|
+
gate: record.gate,
|
|
458
|
+
expiresAt: record.expiresAt,
|
|
459
|
+
...(record.description ? { description: record.description } : {}),
|
|
460
|
+
...(record.url ? { url: record.url } : {}),
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
// A gate a command decided inside an Op's step (#2779) is recorded under
|
|
465
|
+
// the command's op, which is not an `*.op.ts`, so the loop above never
|
|
466
|
+
// reads it. The Op's newest run names it; list it against that Op while it
|
|
467
|
+
// still stands.
|
|
468
|
+
for (const [opName, discovered] of [...allOps.entries()].sort(([a], [b]) => a.localeCompare(b))) {
|
|
469
|
+
let newest;
|
|
470
|
+
try {
|
|
471
|
+
newest = (await readRunLedger(runEnvOf(discovered.config), opName)).records.at(-1);
|
|
472
|
+
}
|
|
473
|
+
catch {
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
const gateOp = newest?.status === "gated" ? newest.gate?.op : undefined;
|
|
477
|
+
if (!newest?.gate || !gateOp || gateOp === opName)
|
|
478
|
+
continue;
|
|
479
|
+
const record = (await pendingGatesFor(gateOp)).find((r) => r.gate === newest.gate.name);
|
|
480
|
+
if (!record || covered.has(`${gateOp} ${record.gate}`))
|
|
481
|
+
continue;
|
|
482
|
+
covered.add(`${gateOp} ${record.gate}`);
|
|
483
|
+
standalone.push({
|
|
484
|
+
op: gateOp,
|
|
485
|
+
gate: record.gate,
|
|
486
|
+
run: opName,
|
|
487
|
+
expiresAt: record.expiresAt,
|
|
488
|
+
...(record.description ? { description: record.description } : {}),
|
|
489
|
+
...(record.url ? { url: record.url } : {}),
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
if (ops.length === 0 && standalone.length === 0) {
|
|
493
|
+
console.error(formatWarning({ message: "No ConvergeOp declarations found" }));
|
|
494
|
+
return 0;
|
|
495
|
+
}
|
|
496
|
+
if (ctx.args.json) {
|
|
497
|
+
console.log(JSON.stringify(standalone.length > 0 ? { rows, pendingGates: standalone } : rows, null, 2));
|
|
498
|
+
return 0;
|
|
499
|
+
}
|
|
500
|
+
for (const row of rows) {
|
|
501
|
+
console.log(formatBold(`${row.op} (${row.env})`));
|
|
502
|
+
if (!row.lastTick) {
|
|
503
|
+
console.log(" no ticks recorded yet");
|
|
504
|
+
}
|
|
505
|
+
else {
|
|
506
|
+
console.log(` last tick : ${row.lastTick.timestamp}`);
|
|
507
|
+
console.log(` ${row.lastTick.log}`);
|
|
508
|
+
}
|
|
509
|
+
console.log(` lease : ${row.lease ? `held by ${row.lease.holder} (expires ${row.lease.expiresAt})` : "free"}`);
|
|
510
|
+
if (row.pendingGates.length > 0) {
|
|
511
|
+
console.log(` pending gates:`);
|
|
512
|
+
for (const g of row.pendingGates) {
|
|
513
|
+
const op = g.op ?? row.op;
|
|
514
|
+
console.log(` - ${op} gate "${g.gate}"${g.rule ? ` (rule ${g.rule})` : ""} — resolve: chant approve ${op} ${g.gate}`);
|
|
515
|
+
if (g.expiresAt)
|
|
516
|
+
console.log(` expires: ${g.expiresAt}`);
|
|
517
|
+
if (g.url)
|
|
518
|
+
console.log(` approve at: ${g.url}`);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
console.log("");
|
|
522
|
+
}
|
|
523
|
+
if (standalone.length > 0) {
|
|
524
|
+
console.log(formatBold("pending gates (no converge tick)"));
|
|
525
|
+
for (const g of standalone) {
|
|
526
|
+
console.log(` - ${g.op} gate "${g.gate}"${g.run ? ` (Op ${g.run}'s run stopped here)` : ""} — resolve: chant approve ${g.op} ${g.gate}`);
|
|
527
|
+
if (g.description)
|
|
528
|
+
console.log(` ${g.description}`);
|
|
529
|
+
console.log(` expires: ${g.expiresAt}`);
|
|
530
|
+
if (g.url)
|
|
531
|
+
console.log(` approve at: ${g.url}`);
|
|
532
|
+
}
|
|
533
|
+
console.log("");
|
|
534
|
+
}
|
|
535
|
+
return 0;
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Gather the converge/gate history a set of ConvergeOps has recorded, merged
|
|
539
|
+
* into one timestamp-ordered timeline, oldest first.
|
|
540
|
+
*
|
|
541
|
+
* `since` (inclusive) and `limit` are applied after the merge — `limit` keeps
|
|
542
|
+
* the *newest* n entries, since that is what a log is asked for, and then
|
|
543
|
+
* prints them oldest-first like every other log.
|
|
544
|
+
*/
|
|
545
|
+
export async function collectOperatorLog(ops, opts = {}) {
|
|
546
|
+
const wanted = opts.op ? ops.filter((o) => o.name === opts.op) : ops;
|
|
547
|
+
const sinceMs = opts.since ? new Date(opts.since).getTime() : undefined;
|
|
548
|
+
const entries = [];
|
|
549
|
+
const malformed = { converge: 0, gates: 0, runs: 0 };
|
|
550
|
+
// One read per distinct environment — several ConvergeOps can share one
|
|
551
|
+
// `<env>/converge.jsonl`, and re-reading it per op would both cost more and
|
|
552
|
+
// double-count its malformed lines.
|
|
553
|
+
const envs = [...new Set(wanted.map((o) => o.env))].sort();
|
|
554
|
+
const opNames = new Set(wanted.map((o) => o.name));
|
|
555
|
+
const gateOps = new Set();
|
|
556
|
+
for (const env of envs) {
|
|
557
|
+
const { records, malformed: bad } = await readConvergeLedger(env, { cwd: opts.cwd });
|
|
558
|
+
malformed.converge += bad;
|
|
559
|
+
for (const record of records) {
|
|
560
|
+
if (!opNames.has(record.op))
|
|
561
|
+
continue;
|
|
562
|
+
for (const outcome of record.outcomes) {
|
|
563
|
+
if (outcome.action === "gated" && outcome.op)
|
|
564
|
+
gateOps.add(outcome.op);
|
|
565
|
+
}
|
|
566
|
+
entries.push({ kind: "tick", timestamp: record.timestamp, record });
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
for (const gateOp of [...gateOps].sort()) {
|
|
570
|
+
const { records, malformed: bad } = await readGateResolutions(gateOp, { cwd: opts.cwd });
|
|
571
|
+
malformed.gates += bad;
|
|
572
|
+
for (const record of records) {
|
|
573
|
+
entries.push({ kind: "gate-resolution", timestamp: record.timestamp, record });
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
// The runs each discovered Op recorded (#2118). Keyed `<env>/runs__<op>`,
|
|
577
|
+
// so unlike the converge ledger this is one read per (env, op) pair rather
|
|
578
|
+
// than per env — a ConvergeOp's own ticks and its own runs interleave here.
|
|
579
|
+
for (const { name, env } of wanted) {
|
|
580
|
+
const { records, malformed: bad } = await readRunLedger(env, name, { cwd: opts.cwd });
|
|
581
|
+
malformed.runs += bad;
|
|
582
|
+
for (const record of records) {
|
|
583
|
+
entries.push({ kind: "run", timestamp: record.ended, record });
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
// Within one instant: the tick first (it is what dispatched anything else),
|
|
587
|
+
// then the run it dispatched, then the gate resolution against that run.
|
|
588
|
+
const rank = { tick: 0, run: 1, "gate-resolution": 2 };
|
|
589
|
+
let merged = entries.sort((a, b) => {
|
|
590
|
+
const delta = new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime();
|
|
591
|
+
return delta !== 0 ? delta : rank[a.kind] - rank[b.kind];
|
|
592
|
+
});
|
|
593
|
+
if (sinceMs !== undefined)
|
|
594
|
+
merged = merged.filter((e) => new Date(e.timestamp).getTime() >= sinceMs);
|
|
595
|
+
if (opts.limit !== undefined && merged.length > opts.limit)
|
|
596
|
+
merged = merged.slice(-opts.limit);
|
|
597
|
+
return { entries: merged, malformed };
|
|
598
|
+
}
|
|
599
|
+
function renderLogEntry(entry) {
|
|
600
|
+
if (entry.kind === "run") {
|
|
601
|
+
const { record } = entry;
|
|
602
|
+
const id = `[${record.id.slice(0, 8)}]`;
|
|
603
|
+
const outcomes = Object.entries(record.outcomes).map(([k, v]) => `${k}=${String(v)}`).join(" ");
|
|
604
|
+
const steps = record.phases.reduce((n, p) => n + p.steps.length, 0);
|
|
605
|
+
return [
|
|
606
|
+
`${record.ended} ${record.op}@${record.env} ${id} run ${record.status} ` +
|
|
607
|
+
`phases=${record.phases.length} steps=${steps}${outcomes ? ` ${outcomes}` : ""}`,
|
|
608
|
+
];
|
|
609
|
+
}
|
|
610
|
+
if (entry.kind === "gate-resolution") {
|
|
611
|
+
const { record } = entry;
|
|
612
|
+
return [
|
|
613
|
+
`${record.timestamp} gate-resolved ${record.op}/${record.gate} by ${record.resolvedBy}` +
|
|
614
|
+
(record.url ? ` ${record.url}` : ""),
|
|
615
|
+
];
|
|
616
|
+
}
|
|
617
|
+
const { record } = entry;
|
|
618
|
+
const id = record.id ? `[${record.id.slice(0, 8)}]` : "[--------]";
|
|
619
|
+
const lines = [`${record.timestamp} ${record.op}@${record.env} ${id} ${record.log}`];
|
|
620
|
+
for (const outcome of record.outcomes) {
|
|
621
|
+
if (outcome.action !== "gated" || !outcome.gateName)
|
|
622
|
+
continue;
|
|
623
|
+
lines.push(` gated ${outcome.ruleId} → ${outcome.op ?? "?"} gate "${outcome.gateName}"` +
|
|
624
|
+
(outcome.url ? ` ${outcome.url}` : ""));
|
|
625
|
+
}
|
|
626
|
+
return lines;
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* `chant operator log [--env <env>] [--op <name>] [--since <iso>] [--limit
|
|
630
|
+
* <n>] [--json]` (#2029) — the converge tick history and the gate resolutions
|
|
631
|
+
* against it, read from the `chant/lifecycle` orphan branch alone.
|
|
632
|
+
*
|
|
633
|
+
* `readConvergeLedger` has always returned every tick for an environment;
|
|
634
|
+
* until this its only caller was `operator status`, which threw the history
|
|
635
|
+
* away and kept `.at(-1)`. A consumer wanting more than the newest row had to
|
|
636
|
+
* `git show chant/lifecycle:<env>/converge.jsonl` and parse it — which pins it
|
|
637
|
+
* to the orphan branch's name, the `<env>/converge.jsonl` path convention, the
|
|
638
|
+
* `_gates/<op>.jsonl` convention and the on-disk encoding, none of which are
|
|
639
|
+
* promised contracts. This is the read surface that makes those private again.
|
|
640
|
+
*
|
|
641
|
+
* Read-only: no lease, no dispatch, no daemon. Same `--json` discipline as
|
|
642
|
+
* `operator status` — one JSON document on stdout, diagnostics on stderr.
|
|
643
|
+
*/
|
|
644
|
+
export async function runOperatorLog(ctx) {
|
|
645
|
+
const { ops, errors } = await discoverConvergeOps({ env: ctx.args.env });
|
|
646
|
+
for (const err of errors)
|
|
647
|
+
console.error(formatWarning({ message: err }));
|
|
648
|
+
if (ops.length === 0) {
|
|
649
|
+
console.error(formatWarning({ message: "No ConvergeOp declarations found" }));
|
|
650
|
+
return 0;
|
|
651
|
+
}
|
|
652
|
+
if (ctx.args.since && Number.isNaN(new Date(ctx.args.since).getTime())) {
|
|
653
|
+
console.error(formatError({
|
|
654
|
+
message: `--since must be an ISO-8601 timestamp (got "${ctx.args.since}")`,
|
|
655
|
+
hint: "e.g. --since 2026-01-01T00:00:00Z",
|
|
656
|
+
}));
|
|
657
|
+
return 1;
|
|
658
|
+
}
|
|
659
|
+
if (ctx.args.limit !== undefined && (!Number.isInteger(ctx.args.limit) || ctx.args.limit < 1)) {
|
|
660
|
+
console.error(formatError({ message: `--limit must be a positive integer (got "${ctx.args.limit}")` }));
|
|
661
|
+
return 1;
|
|
662
|
+
}
|
|
663
|
+
const { entries, malformed } = await collectOperatorLog(ops.map((d) => ({ name: d.config.name, env: d.config.labels?.Env ?? "unknown" })), { op: ctx.args.op, since: ctx.args.since, limit: ctx.args.limit });
|
|
664
|
+
if (ctx.args.json) {
|
|
665
|
+
console.log(JSON.stringify({ entries, malformed }, null, 2));
|
|
666
|
+
return 0;
|
|
667
|
+
}
|
|
668
|
+
const unreadable = malformed.converge + malformed.gates + malformed.runs;
|
|
669
|
+
if (unreadable > 0) {
|
|
670
|
+
console.error(formatWarning({
|
|
671
|
+
message: `${unreadable} ledger line(s) were unreadable and are missing from this timeline ` +
|
|
672
|
+
`(converge: ${malformed.converge}, gates: ${malformed.gates}, runs: ${malformed.runs})`,
|
|
673
|
+
}));
|
|
674
|
+
}
|
|
675
|
+
if (entries.length === 0) {
|
|
676
|
+
console.error(formatWarning({ message: "No converge ticks or Op runs recorded yet" }));
|
|
677
|
+
return 0;
|
|
678
|
+
}
|
|
679
|
+
for (const entry of entries) {
|
|
680
|
+
for (const line of renderLogEntry(entry))
|
|
681
|
+
console.log(line);
|
|
682
|
+
}
|
|
683
|
+
return 0;
|
|
684
|
+
}
|
|
685
|
+
// ── chant approve <op> <gate> ───────────────────────────────────────────────
|
|
686
|
+
/**
|
|
687
|
+
* `chant approve <op> <gate> [--actor <name>] [--note <text>] [--url <url>]
|
|
688
|
+
* [--expire]` — record the out-of-band resolution fact for a gate a run
|
|
689
|
+
* stopped at (issue #1485: "resolution is an out-of-band act that writes the
|
|
690
|
+
* counterpart fact"). Per that issue's leaning on open question 3 ("local
|
|
691
|
+
* trust in v1"), this performs no authorization check beyond "you can run
|
|
692
|
+
* `chant approve` locally".
|
|
693
|
+
*
|
|
694
|
+
* Since #2119 this closes the loop rather than just narrating it: the next
|
|
695
|
+
* `chant run <op>` reads the resolution, finds it newer than the gate's
|
|
696
|
+
* pending fact, and walks through.
|
|
697
|
+
*
|
|
698
|
+
* `--expire` is the other half — clear a standing pending fact *without*
|
|
699
|
+
* approving anything, for a gate that was recorded against a run nobody
|
|
700
|
+
* intends to finish. It writes no resolution; it appends a pending fact that
|
|
701
|
+
* is already expired, which supersedes the standing one on an append-only
|
|
702
|
+
* ledger, so the next run decides the gate from scratch.
|
|
703
|
+
*/
|
|
704
|
+
/** The ledger branch, named in the warnings below so a reader can go look at it. */
|
|
705
|
+
const LIFECYCLE_BRANCH = "chant/lifecycle";
|
|
706
|
+
/**
|
|
707
|
+
* Push the ledger and say so when it does not land (#2309 review, refs #2310).
|
|
708
|
+
*
|
|
709
|
+
* Both write paths here used `pushLifecycle().catch(() => undefined)` and then
|
|
710
|
+
* printed unconditional success and exited 0. A rejected push — a stale lease,
|
|
711
|
+
* no credentials, no network — therefore read as a completed approval, which
|
|
712
|
+
* is the one thing an approval must never do: the operator walks away
|
|
713
|
+
* believing the gate is answered for everybody, while the resolution exists
|
|
714
|
+
* only in their own checkout.
|
|
715
|
+
*
|
|
716
|
+
* The append is still a correct *local* fact, so this is a warning and not a
|
|
717
|
+
* failure; the exit code is unchanged. With no remote at all there is nobody
|
|
718
|
+
* elsewhere to tell, so that case is a quiet suffix and no warning (#3677).
|
|
719
|
+
*/
|
|
720
|
+
async function reportedPush(consequence) {
|
|
721
|
+
const { pushed, pushWarning } = await pushLifecycleReport();
|
|
722
|
+
if (pushed)
|
|
723
|
+
return { pushed, suffix: "" };
|
|
724
|
+
if (isLifecycleLocalNote(pushWarning))
|
|
725
|
+
return { pushed, suffix: ` (${LIFECYCLE_LOCAL_NOTE})` };
|
|
726
|
+
console.error(formatWarning({
|
|
727
|
+
message: `The push to the remote did not land: ${pushWarning ?? "no reason given"}`,
|
|
728
|
+
hint: consequence,
|
|
729
|
+
}));
|
|
730
|
+
return { pushed, suffix: " (local only — the push did not land)" };
|
|
731
|
+
}
|
|
732
|
+
export async function runApprove(ctx) {
|
|
733
|
+
const opName = ctx.args.path;
|
|
734
|
+
const gate = ctx.args.extraPositional;
|
|
735
|
+
if (!opName || opName === "." || !gate) {
|
|
736
|
+
console.error(formatError({ message: "Usage: chant approve <op> <gate>" }));
|
|
737
|
+
return 1;
|
|
738
|
+
}
|
|
739
|
+
if (ctx.args.expire) {
|
|
740
|
+
// Same guard as the approve path below (#2303): `--expire` reads the
|
|
741
|
+
// standing fact and appends beside it, so a clone that never fetched the
|
|
742
|
+
// branch would both fail to see the fact it is expiring and replace the
|
|
743
|
+
// branch with the expiry alone.
|
|
744
|
+
try {
|
|
745
|
+
await requireLifecycleLedger();
|
|
746
|
+
}
|
|
747
|
+
catch (err) {
|
|
748
|
+
console.error(formatError({
|
|
749
|
+
message: `Cannot expire the gate: ${err instanceof Error ? err.message : String(err)}`,
|
|
750
|
+
}));
|
|
751
|
+
return 1;
|
|
752
|
+
}
|
|
753
|
+
const now = new Date().toISOString();
|
|
754
|
+
const standing = latestPendingGate((await readGateLedger(opName)).pending, gate);
|
|
755
|
+
if (!standing || isPendingGateExpired(standing, now)) {
|
|
756
|
+
console.error(formatWarning({
|
|
757
|
+
message: `Gate "${gate}" on "${opName}" has no standing pending fact — nothing to expire`,
|
|
758
|
+
}));
|
|
759
|
+
return 0;
|
|
760
|
+
}
|
|
761
|
+
await appendPendingGate({
|
|
762
|
+
op: opName,
|
|
763
|
+
gate,
|
|
764
|
+
timestamp: now,
|
|
765
|
+
expiresAt: now,
|
|
766
|
+
...(standing.description ? { description: standing.description } : {}),
|
|
767
|
+
});
|
|
768
|
+
const push = await reportedPush(`The expiry is recorded locally on ${LIFECYCLE_BRANCH}. Until it reaches the remote, ` +
|
|
769
|
+
`a run in another checkout still sees the old pending fact.`);
|
|
770
|
+
console.error(formatSuccess(`Gate "${gate}" on "${opName}" expired at ${now} — not approved` + push.suffix));
|
|
771
|
+
console.error(formatInfo(opName === FAN_OUT_GATE_OP
|
|
772
|
+
? `The next \`chant components fan-out\` decides this gate from scratch and records a fresh pending fact.`
|
|
773
|
+
: PR_GATE_OP.test(opName)
|
|
774
|
+
? `The next \`chant components pr-apply\` decides this gate from scratch and records a fresh pending fact.`
|
|
775
|
+
: opName === WORKSPACE_UPGRADE_GATE_OP
|
|
776
|
+
? `The next \`chant workspace upgrade ${gate}\` decides this gate from scratch and records a fresh pending fact.`
|
|
777
|
+
: `The next \`chant run ${opName}\` decides this gate from scratch and records a fresh pending fact.`));
|
|
778
|
+
return 0;
|
|
779
|
+
}
|
|
780
|
+
// #3760: an override of a denied plan names its rules and gives a reason.
|
|
781
|
+
if (opName === OVERRIDE_GATE_OP) {
|
|
782
|
+
const { runOverrideApprove } = await import("./override-approve.js");
|
|
783
|
+
return runOverrideApprove(ctx);
|
|
784
|
+
}
|
|
785
|
+
if (ctx.args.rules !== undefined || ctx.args.reason !== undefined) {
|
|
786
|
+
console.error(formatError({
|
|
787
|
+
message: `--rule and --reason override a denied plan: chant approve ${OVERRIDE_GATE_OP} <subject> --rule <id> --reason <text>`,
|
|
788
|
+
hint: `To approve gate "${gate}" on "${opName}", put any prose in --note.`,
|
|
789
|
+
}));
|
|
790
|
+
return 1;
|
|
791
|
+
}
|
|
792
|
+
// `--approver` wins over `--actor`, the same precedence `chant run approve`
|
|
793
|
+
// has applied since #2126 (`../handlers/run.ts`'s `runOpApprove`). Reading
|
|
794
|
+
// only `--actor` here meant every README that teaches `--approver you`
|
|
795
|
+
// recorded the shell user instead (#2192).
|
|
796
|
+
const outcome = await recordGateApproval(opName, gate, {
|
|
797
|
+
actor: ctx.args.approver ?? ctx.args.actor,
|
|
798
|
+
note: ctx.args.note,
|
|
799
|
+
url: ctx.args.url,
|
|
800
|
+
plan: ctx.args.plan,
|
|
801
|
+
...(ctx.args.env !== undefined ? { environment: ctx.args.env } : {}),
|
|
802
|
+
allowSameOrigin: ctx.args.allowSameOrigin,
|
|
803
|
+
...(ctx.args.roles ? { roles: ctx.args.roles } : {}),
|
|
804
|
+
...(ctx.args.agent ? { agent: true } : {}),
|
|
805
|
+
...(ctx.args.sign !== undefined ? { sign: ctx.args.sign } : {}),
|
|
806
|
+
...(ctx.args.relayedBy !== undefined ? { relayedBy: ctx.args.relayedBy } : {}),
|
|
807
|
+
});
|
|
808
|
+
if (!outcome.ok)
|
|
809
|
+
return 1;
|
|
810
|
+
// #3683: start the CI job that waits at this gate again.
|
|
811
|
+
if (ctx.args.resumeRun || ctx.args.resume !== undefined) {
|
|
812
|
+
const { resumeAfterApproval } = await import("./gate-resume.js");
|
|
813
|
+
return resumeAfterApproval(opName, gate, outcome.record.environment, outcome.pushed);
|
|
814
|
+
}
|
|
815
|
+
console.error(formatInfo(`This records the resolution as a fact; it does not itself re-run anything. ` +
|
|
816
|
+
(opName === FAN_OUT_GATE_OP
|
|
817
|
+
? `Repeat the \`chant components fan-out\` command and it walks through gate "${gate}".`
|
|
818
|
+
: PR_GATE_OP.test(opName)
|
|
819
|
+
? `The pull request's apply (\`chant components pr-apply\`) walks through gate "${gate}" when it plans this same digest.`
|
|
820
|
+
: opName === WORKSPACE_UPGRADE_GATE_OP
|
|
821
|
+
? `Repeat \`chant workspace upgrade ${gate}\` with the same target, and it applies the patch this approval names.`
|
|
822
|
+
: `Run \`chant run ${opName}\` and it walks through gate "${gate}".`)));
|
|
823
|
+
return 0;
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* The ledger write behind `chant approve` — extracted so `chant run approve`
|
|
827
|
+
* (#2121) records the identical fact before waking the runtime that hosts the
|
|
828
|
+
* gated run, instead of growing a second, drifting writer.
|
|
829
|
+
*
|
|
830
|
+
* Prints the same warning for an undiscovered op and the same success line as
|
|
831
|
+
* `chant approve` always did; the caller adds whatever it does next.
|
|
832
|
+
*
|
|
833
|
+
* Since #2300 the resolution names the plan it approves. By default that is
|
|
834
|
+
* the standing pending fact's plan — the one the gated run produced — so the
|
|
835
|
+
* common path is still `chant run`, read, `chant approve`. With no pending
|
|
836
|
+
* fact and no `--plan`, this refuses: there is nothing on the ledger saying
|
|
837
|
+
* what would be approved, and writing a resolution anyway is what made an
|
|
838
|
+
* approval mean "the next run" instead of "this plan".
|
|
839
|
+
*/
|
|
840
|
+
export async function recordGateApproval(opName, gate, opts) {
|
|
841
|
+
const { ops } = await discoverOps();
|
|
842
|
+
// `fan-out` is the op name every `chant components fan-out` gate is recorded
|
|
843
|
+
// under (../handlers/fan-out.ts). It is a command rather than a declaration,
|
|
844
|
+
// so there is no `*.op.ts` to find and nothing is wrong when none is there.
|
|
845
|
+
if (!ops.has(opName) && opName !== FAN_OUT_GATE_OP && opName !== WORKSPACE_UPGRADE_GATE_OP && opName !== OVERRIDE_GATE_OP) {
|
|
846
|
+
console.error(formatWarning({
|
|
847
|
+
message: `Op "${opName}" was not found among discovered *.op.ts declarations — recording the resolution anyway`,
|
|
848
|
+
}));
|
|
849
|
+
}
|
|
850
|
+
// Read the branch before appending to it (#2303 finding 2). Without this,
|
|
851
|
+
// an approve in a clone that never fetched `chant/lifecycle` builds its
|
|
852
|
+
// commit from an empty tree with no parent and replaces the branch with a
|
|
853
|
+
// single commit holding only this resolution — the pending fact it is
|
|
854
|
+
// answering is discarded rather than appended to.
|
|
855
|
+
try {
|
|
856
|
+
await requireLifecycleLedger();
|
|
857
|
+
}
|
|
858
|
+
catch (err) {
|
|
859
|
+
console.error(formatError({
|
|
860
|
+
message: `Cannot record the approval: ${err instanceof Error ? err.message : String(err)}`,
|
|
861
|
+
hint: "The pending fact this answers lives on that branch; appending without it would drop it.",
|
|
862
|
+
}));
|
|
863
|
+
return { ok: false };
|
|
864
|
+
}
|
|
865
|
+
// #2028: the resolution's link is typed. `--url` wins; otherwise, running
|
|
866
|
+
// inside the PR/MR job that carries the change is itself the address, the
|
|
867
|
+
// same env fallback `--actor` uses. `--note` stays free-text prose.
|
|
868
|
+
const url = opts.url ?? resolveApprovalUrl();
|
|
869
|
+
if (url && !isApprovalUrl(url)) {
|
|
870
|
+
console.error(formatError({
|
|
871
|
+
message: `--url must be an absolute http/https URL (got "${url}")`,
|
|
872
|
+
hint: "Pass the PR/MR link, or omit --url and put prose in --note.",
|
|
873
|
+
}));
|
|
874
|
+
return { ok: false };
|
|
875
|
+
}
|
|
876
|
+
// #2300: an approval is for a plan, so this command has to know which one.
|
|
877
|
+
// `--plan` names it outright; otherwise it comes off the gate's standing
|
|
878
|
+
// pending fact — the plan the run that stopped at this gate produced, which
|
|
879
|
+
// is the plan whoever is typing this just read. There is deliberately no
|
|
880
|
+
// third fallback: approving with no plan in sight is the behaviour
|
|
881
|
+
// INTENTIUS/choudoufu#1026 measured, where the resolution authorised the
|
|
882
|
+
// next run rather than anything anyone had seen.
|
|
883
|
+
// #2574: a component gate's pending facts carry the environment they were
|
|
884
|
+
// recorded in, and an approval answers one environment. `--env` picks it;
|
|
885
|
+
// without it, the newest standing fact decides, and the success line below
|
|
886
|
+
// names the environment it chose.
|
|
887
|
+
const ledgerBefore = await readGateLedger(opName);
|
|
888
|
+
const pendingFor = (all) => {
|
|
889
|
+
const bound = all.some((p) => p.gate === gate && p.environment !== undefined);
|
|
890
|
+
return bound && opts.environment !== undefined ? all.filter((p) => p.environment === opts.environment) : all;
|
|
891
|
+
};
|
|
892
|
+
const standing = latestPendingGate(pendingFor(ledgerBefore.pending), gate);
|
|
893
|
+
const environment = standing?.environment;
|
|
894
|
+
let planDigest;
|
|
895
|
+
if (opts.plan !== undefined) {
|
|
896
|
+
if (!isPlanDigest(opts.plan)) {
|
|
897
|
+
console.error(formatError({
|
|
898
|
+
message: `--plan must be a plan digest ("jcs1-sha256:" or "sha256:", then 64 hex characters), got "${opts.plan}"`,
|
|
899
|
+
hint: "Copy it from the gated run's `plan :` line, or from the pending-gate summary. It is not a plan file path.",
|
|
900
|
+
}));
|
|
901
|
+
return { ok: false };
|
|
902
|
+
}
|
|
903
|
+
planDigest = opts.plan;
|
|
904
|
+
}
|
|
905
|
+
else {
|
|
906
|
+
if (!standing) {
|
|
907
|
+
console.error(formatError({
|
|
908
|
+
message: opts.environment !== undefined && ledgerBefore.pending.some((p) => p.gate === gate && p.environment !== undefined)
|
|
909
|
+
? `Gate "${gate}" on "${opName}" has no pending fact in environment "${opts.environment}", so there is no plan to approve`
|
|
910
|
+
: `Gate "${gate}" on "${opName}" has no pending fact, so there is no plan to approve`,
|
|
911
|
+
hint: `Run \`chant run ${opName}\` first — it plans, stops at the gate, and records the plan this ` +
|
|
912
|
+
`approval would be for. To approve a plan you already have the digest for, pass ` +
|
|
913
|
+
`\`--plan <digest>\`.`,
|
|
914
|
+
}));
|
|
915
|
+
return { ok: false };
|
|
916
|
+
}
|
|
917
|
+
planDigest = standing.planDigest;
|
|
918
|
+
}
|
|
919
|
+
// chant#2384 — the gate's whole value is that its two halves have different
|
|
920
|
+
// authors. At a shell they do: a person runs, reads the plan, and approves.
|
|
921
|
+
// On MCP and ACP the person's only act was launching the server, and every
|
|
922
|
+
// call after is the model's, so a gate reached over one of those channels and
|
|
923
|
+
// resolved over the same one has no second party in it. Refused by default,
|
|
924
|
+
// the way #2300 refuses a plan nobody approved.
|
|
925
|
+
const origin = opts.origin ?? currentGateOrigin();
|
|
926
|
+
const ledger = await readGateLedger(opName);
|
|
927
|
+
const standingForOrigin = latestPendingGate(pendingFor(ledger.pending), gate);
|
|
928
|
+
// chant#3485: a gate that is never resolved over MCP or ACP says so on its
|
|
929
|
+
// own record, or on its Op's config. No flag overrides this one.
|
|
930
|
+
if (isModelAuthored(origin) && (standingForOrigin?.neverOverMcp || ops.get(opName)?.config.neverOverMcp)) {
|
|
931
|
+
console.error(formatError({
|
|
932
|
+
message: `Gate "${gate}" on "${opName}" is never resolved over ${origin === "acp" ? "ACP" : "MCP"}`,
|
|
933
|
+
hint: `Approvals belong to people: \`chant approve ${opName} ${gate}\` at a shell.`,
|
|
934
|
+
}));
|
|
935
|
+
return { ok: false };
|
|
936
|
+
}
|
|
937
|
+
// #3170: a gate that asks a decision point passes on the point's answer,
|
|
938
|
+
// which the run cites on the ledger. An approval here would count for nothing.
|
|
939
|
+
if (standingForOrigin?.answer) {
|
|
940
|
+
console.error(formatError({
|
|
941
|
+
message: `Gate "${gate}" on "${opName}" asks decision point ${standingForOrigin.answer.point}, so its answer passes the gate, not an approval`,
|
|
942
|
+
hint: `Answer the question: \`chant workspace points answer ${standingForOrigin.answer.id} --answer <value> --by <name>\`, then run \`chant run ${opName}\` again.`,
|
|
943
|
+
}));
|
|
944
|
+
return { ok: false };
|
|
945
|
+
}
|
|
946
|
+
const refusal = sameOriginRefusal(standingForOrigin?.origin, origin);
|
|
947
|
+
if (refusal && !opts.allowSameOrigin) {
|
|
948
|
+
console.error(formatError({
|
|
949
|
+
message: `Gate "${gate}" on "${opName}" cannot be resolved from here: ${refusal}`,
|
|
950
|
+
hint: `Approve it from a channel the run did not come from — \`chant approve ${opName} ${gate}\` ` +
|
|
951
|
+
`at a shell is the usual one. If resolving from the same channel genuinely is the intent, ` +
|
|
952
|
+
`pass --allow-same-origin, which records that it was deliberate.`,
|
|
953
|
+
}));
|
|
954
|
+
return { ok: false };
|
|
955
|
+
}
|
|
956
|
+
// #3402: the relay is a principal, on one line (the seal's payload is LF-joined).
|
|
957
|
+
const relayedBy = opts.relayedBy?.trim();
|
|
958
|
+
if (opts.relayedBy !== undefined && (!relayedBy || /[\r\n]/.test(relayedBy))) {
|
|
959
|
+
console.error(formatError({
|
|
960
|
+
message: `--relayed-by needs one principal on one line (got ${JSON.stringify(opts.relayedBy)})`,
|
|
961
|
+
hint: "Name who relayed the approval, such as --relayed-by github:<login> or a principal the signers file lists.",
|
|
962
|
+
}));
|
|
963
|
+
return { ok: false };
|
|
964
|
+
}
|
|
965
|
+
const resolvedBy = opts.actor
|
|
966
|
+
?? (isModelAuthored(origin)
|
|
967
|
+
// A channel that cannot attest to a name does not get to write one down.
|
|
968
|
+
// "unattested" reads as what it is; a name the model chose would read in
|
|
969
|
+
// the ledger exactly like a name a person gave.
|
|
970
|
+
? UNATTESTED_APPROVER
|
|
971
|
+
: process.env.GITHUB_ACTOR ?? process.env.GITLAB_USER_LOGIN ?? process.env.USER ?? "unknown");
|
|
972
|
+
// #2508: who this is, as the gate's quorum and policy read it. A model on
|
|
973
|
+
// MCP or ACP is an agent whatever it claims, the same reasoning that gives
|
|
974
|
+
// it `UNATTESTED_APPROVER` for a name.
|
|
975
|
+
const approver = {
|
|
976
|
+
kind: opts.agent || isModelAuthored(origin) ? "agent" : "human",
|
|
977
|
+
...(opts.roles && opts.roles.length > 0 ? { roles: [...new Set(opts.roles)] } : {}),
|
|
978
|
+
};
|
|
979
|
+
// #3163, ws-080: the declaration at base may require a checkable approver
|
|
980
|
+
// (identity.attribution "identified") and a signed approval for this gate
|
|
981
|
+
// (identity.gates). Both are judged before anything is written.
|
|
982
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
983
|
+
const identity = await import("../../workspace/identity.js");
|
|
984
|
+
const { scopeSource } = await import("../../workspace/write-scope.js");
|
|
985
|
+
const scope = scopeSource(cwd);
|
|
986
|
+
try {
|
|
987
|
+
identity.refuseUnidentified(scope, [resolvedBy], opts.actor !== undefined ? "--actor" : "the approver");
|
|
988
|
+
if (relayedBy)
|
|
989
|
+
identity.refuseUnidentified(scope, [relayedBy], "--relayed-by");
|
|
990
|
+
}
|
|
991
|
+
catch (err) {
|
|
992
|
+
if (!(err instanceof identity.IdentityError))
|
|
993
|
+
throw err;
|
|
994
|
+
console.error(formatError({
|
|
995
|
+
message: `Gate "${gate}" on "${opName}" was not approved: ${err.message}`,
|
|
996
|
+
hint: "Pass --actor <forge identity or signer>, as the surface that signed the person in maps them, for example --actor github:<login>.",
|
|
997
|
+
}));
|
|
998
|
+
return { ok: false };
|
|
999
|
+
}
|
|
1000
|
+
const rule = identity.gateAdmissionFrom(scope, gate);
|
|
1001
|
+
if (rule && opts.sign === undefined) {
|
|
1002
|
+
console.error(formatError({
|
|
1003
|
+
message: `Gate "${gate}" needs a signed approval: chant.workspace.json at base names it in identity.gates` +
|
|
1004
|
+
(rule.requirement.class !== null ? `, from the class ${rule.requirement.class}` : "") +
|
|
1005
|
+
`, and this approval is not signed`,
|
|
1006
|
+
hint: `chant approve ${opName} ${gate} --actor <principal> --sign [<key file>], with a key ${scope.policy.signersPath} at base lists for that principal.`,
|
|
1007
|
+
}));
|
|
1008
|
+
return { ok: false };
|
|
1009
|
+
}
|
|
1010
|
+
const timestamp = new Date().toISOString();
|
|
1011
|
+
let seal;
|
|
1012
|
+
if (opts.sign !== undefined) {
|
|
1013
|
+
const { resolveSigningKey, sealGateApproval, SealError } = await import("../../workspace/trust/seal.js");
|
|
1014
|
+
try {
|
|
1015
|
+
const key = resolveSigningKey(opts.sign, cwd);
|
|
1016
|
+
try {
|
|
1017
|
+
seal = sealGateApproval(key.file, {
|
|
1018
|
+
op: opName,
|
|
1019
|
+
gate,
|
|
1020
|
+
...(environment !== undefined ? { environment } : {}),
|
|
1021
|
+
...(planDigest !== undefined ? { planDigest } : {}),
|
|
1022
|
+
resolvedBy,
|
|
1023
|
+
...(relayedBy ? { relayedBy } : {}),
|
|
1024
|
+
timestamp,
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
finally {
|
|
1028
|
+
key.cleanup();
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
catch (err) {
|
|
1032
|
+
if (!(err instanceof SealError))
|
|
1033
|
+
throw err;
|
|
1034
|
+
console.error(formatError({ message: `Gate "${gate}" on "${opName}" was not approved: ${err.message}` }));
|
|
1035
|
+
return { ok: false };
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
if (rule) {
|
|
1039
|
+
const why = rule.refuses({
|
|
1040
|
+
op: opName,
|
|
1041
|
+
gate,
|
|
1042
|
+
...(environment !== undefined ? { environment } : {}),
|
|
1043
|
+
...(planDigest !== undefined ? { planDigest } : {}),
|
|
1044
|
+
resolvedBy,
|
|
1045
|
+
...(relayedBy ? { relayedBy } : {}),
|
|
1046
|
+
timestamp,
|
|
1047
|
+
seal,
|
|
1048
|
+
});
|
|
1049
|
+
if (why !== null) {
|
|
1050
|
+
console.error(formatError({
|
|
1051
|
+
message: `Gate "${gate}" on "${opName}" was not approved: ${why}`,
|
|
1052
|
+
hint: `identity.gates.${gate} in chant.workspace.json at base counts only an approval signed by a key ${scope.policy.signersPath} lists for the approver` +
|
|
1053
|
+
(rule.requirement.class !== null ? `, who holds the role of the class ${rule.requirement.class}` : "") + ".",
|
|
1054
|
+
}));
|
|
1055
|
+
return { ok: false };
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
// #2508: a gate with a policy has it evaluated for this approval now, while
|
|
1059
|
+
// the approver is known, against the context the gated run recorded. The
|
|
1060
|
+
// decision is written next to the approval, so a pass can be traced to the
|
|
1061
|
+
// rule that allowed it.
|
|
1062
|
+
const approval = standingForOrigin?.approval;
|
|
1063
|
+
let policyDecision;
|
|
1064
|
+
if (approval?.policy) {
|
|
1065
|
+
try {
|
|
1066
|
+
const evaluator = opts.evaluator ?? (await loadGatePolicyEvaluator(approval.policy.lexicon));
|
|
1067
|
+
const answer = await evaluator.evaluateGatePolicy(approval.policy, gatePolicyRequest({
|
|
1068
|
+
op: opName,
|
|
1069
|
+
gate,
|
|
1070
|
+
resolvedBy,
|
|
1071
|
+
approver,
|
|
1072
|
+
...(planDigest !== undefined ? { planDigest } : {}),
|
|
1073
|
+
...(approval.context ? { context: approval.context } : {}),
|
|
1074
|
+
}));
|
|
1075
|
+
policyDecision = {
|
|
1076
|
+
policy: approval.policy.name,
|
|
1077
|
+
version: approval.policy.version,
|
|
1078
|
+
mode: approval.mode,
|
|
1079
|
+
contextDigest: gateContextDigest(approval.context),
|
|
1080
|
+
decision: answer.decision,
|
|
1081
|
+
determining: answer.determining,
|
|
1082
|
+
errors: answer.errors,
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
catch (err) {
|
|
1086
|
+
console.error(formatError({
|
|
1087
|
+
message: `Gate "${gate}" on "${opName}" declares policy "${approval.policy.name}", which could not be evaluated: ${err instanceof Error ? err.message : String(err)}`,
|
|
1088
|
+
hint: `Install the ${approval.policy.lexicon} lexicon in this project, or approve from a checkout that has it.`,
|
|
1089
|
+
}));
|
|
1090
|
+
return { ok: false };
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
const { record } = await appendGateResolution({
|
|
1094
|
+
op: opName,
|
|
1095
|
+
gate,
|
|
1096
|
+
resolvedBy,
|
|
1097
|
+
...(relayedBy ? { relayedBy } : {}),
|
|
1098
|
+
timestamp,
|
|
1099
|
+
...(seal ? { seal } : {}),
|
|
1100
|
+
approver,
|
|
1101
|
+
...(policyDecision ? { policyDecision } : {}),
|
|
1102
|
+
...(opts.note ? { note: opts.note } : {}),
|
|
1103
|
+
...(url ? { url } : {}),
|
|
1104
|
+
...(planDigest !== undefined ? { planDigest } : {}),
|
|
1105
|
+
...(environment !== undefined ? { environment } : {}),
|
|
1106
|
+
origin,
|
|
1107
|
+
...(refusal && opts.allowSameOrigin ? { sameOriginOverride: true } : {}),
|
|
1108
|
+
});
|
|
1109
|
+
const push = await reportedPush(`The resolution is recorded locally on ${LIFECYCLE_BRANCH}. Until it reaches the remote, ` +
|
|
1110
|
+
`a run in another checkout will not see it.`);
|
|
1111
|
+
console.error(formatSuccess(`Gate "${gate}" on "${opName}" resolved by ${record.resolvedBy}` +
|
|
1112
|
+
(record.relayedBy ? `, relayed by ${record.relayedBy},` : "") +
|
|
1113
|
+
` at ${record.timestamp}` +
|
|
1114
|
+
(record.url ? ` (${record.url})` : "") +
|
|
1115
|
+
(record.seal ? `, signed with ${record.seal.key}` : "") +
|
|
1116
|
+
push.suffix));
|
|
1117
|
+
if (record.planDigest) {
|
|
1118
|
+
console.error(formatInfo(`This approves the plan ${record.planDigest}` +
|
|
1119
|
+
(record.environment !== undefined ? ` in environment "${record.environment}"` : "") +
|
|
1120
|
+
", and only that plan. A run whose fresh plan differs refuses rather than applying it."));
|
|
1121
|
+
}
|
|
1122
|
+
if (approval) {
|
|
1123
|
+
for (const line of describeApprovalProgress(approval, record, ledger.resolutions, standingForOrigin, ledger.pending)) {
|
|
1124
|
+
console.error(formatInfo(line));
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
return { ok: true, record, pushed: push.pushed };
|
|
1128
|
+
}
|
|
1129
|
+
/**
|
|
1130
|
+
* What a just-recorded approval did to a gate with an `approval` block
|
|
1131
|
+
* (#2508): the policy's decision and whether it binds, then the quorum count
|
|
1132
|
+
* for this plan. The run decides the gate from the ledger; these lines only
|
|
1133
|
+
* say what it will find.
|
|
1134
|
+
*/
|
|
1135
|
+
export function describeApprovalProgress(approval, record, prior, standing, pending = standing ? [standing] : []) {
|
|
1136
|
+
const lines = [];
|
|
1137
|
+
const decision = record.policyDecision;
|
|
1138
|
+
if (decision) {
|
|
1139
|
+
const rules = decision.determining.length > 0 ? ` (${decision.determining.join(", ")})` : "";
|
|
1140
|
+
const binds = approval.mode === "enforce"
|
|
1141
|
+
? decision.decision === "allow"
|
|
1142
|
+
? "In enforce mode this permit passes the gate on its own."
|
|
1143
|
+
: "In enforce mode only a permit passes the gate on its own, so this approval counts only toward the quorum."
|
|
1144
|
+
: "The gate is in log-only mode, so the decision is recorded and does not change the outcome.";
|
|
1145
|
+
lines.push(`Policy "${decision.policy}" ${decision.version}: ${decision.decision}${rules}. ${binds}`);
|
|
1146
|
+
for (const error of decision.errors)
|
|
1147
|
+
lines.push(`Policy evaluation error: ${error}`);
|
|
1148
|
+
}
|
|
1149
|
+
const since = standing?.timestamp ?? new Date(0).toISOString();
|
|
1150
|
+
// #3206: count the humans the way the run will, back past facts re-recorded for this plan.
|
|
1151
|
+
const humansSince = standing && samePlanDigest(standing.planDigest, record.planDigest) ? humanQuorumSince(pending, standing) : since;
|
|
1152
|
+
const tally = tallyGateApprovals([...prior, record], record.gate, since, record.planDigest, approval, humansSince);
|
|
1153
|
+
const roles = approval.quorum?.roles ? ` with role ${approval.quorum.roles.join(" or ")}` : "";
|
|
1154
|
+
const who = tally.counted.map((r) => r.resolvedBy).join(", ") || "none yet";
|
|
1155
|
+
lines.push(`Quorum: ${tally.counted.length} of ${tally.need} human approval(s)${roles} for this plan (${who}).`);
|
|
1156
|
+
if (!tally.counted.includes(record)) {
|
|
1157
|
+
lines.push(approverOf(record).kind === "agent"
|
|
1158
|
+
? "An agent's approval does not count toward the quorum."
|
|
1159
|
+
: `This approval does not count toward the quorum: the approver claims none of its roles (${approval.quorum?.roles?.join(", ")}).`);
|
|
1160
|
+
}
|
|
1161
|
+
return lines;
|
|
1162
|
+
}
|