@intentius/chant 0.101.0 → 0.104.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/checks.d.ts.map +1 -1
- package/dist/attrref.d.ts.map +1 -1
- package/dist/audit/core.d.ts +2 -0
- package/dist/audit/core.d.ts.map +1 -1
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/audit/nginx.d.ts.map +1 -1
- package/dist/audit/prior-art.d.ts +48 -0
- package/dist/audit/prior-art.d.ts.map +1 -1
- package/dist/behaviour-kinds.d.ts.map +1 -1
- package/dist/builder.d.ts.map +1 -1
- package/dist/change-set.d.ts +232 -0
- package/dist/change-set.d.ts.map +1 -0
- package/dist/cli/commands/audit.d.ts +7 -0
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/carve-emit.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon.d.ts +16 -0
- package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/import.d.ts +8 -0
- package/dist/cli/commands/import.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/init.d.ts +19 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/onboard.d.ts +9 -0
- package/dist/cli/commands/onboard.d.ts.map +1 -1
- package/dist/cli/commands/pinned-upgrade.d.ts +19 -5
- package/dist/cli/commands/pinned-upgrade.d.ts.map +1 -1
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/change-set.d.ts +11 -0
- package/dist/cli/handlers/change-set.d.ts.map +1 -0
- package/dist/cli/handlers/dev.d.ts.map +1 -1
- package/dist/cli/handlers/fan-out.d.ts +33 -0
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/misc.d.ts.map +1 -1
- package/dist/cli/handlers/operator.d.ts +16 -0
- package/dist/cli/handlers/operator.d.ts.map +1 -1
- package/dist/cli/handlers/pr.d.ts +31 -0
- package/dist/cli/handlers/pr.d.ts.map +1 -0
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/lsp/server.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/op-tools.d.ts.map +1 -1
- package/dist/cli/mcp/server.d.ts.map +1 -1
- package/dist/cli/mcp/tools/build.d.ts.map +1 -1
- package/dist/cli/mcp/tools/composites.d.ts.map +1 -1
- package/dist/cli/mcp/tools/explain.d.ts.map +1 -1
- package/dist/cli/mcp/tools/import.d.ts.map +1 -1
- package/dist/cli/mcp/tools/lint.d.ts.map +1 -1
- package/dist/cli/mcp/tools/scaffold.d.ts.map +1 -1
- package/dist/cli/mcp/tools/search.d.ts.map +1 -1
- package/dist/cli/mcp/workspace-tools.d.ts +4 -3
- package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +74 -3
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/fetch.d.ts.map +1 -1
- package/dist/codegen/naming.d.ts.map +1 -1
- package/dist/codegen/pinned-upgrade.d.ts +67 -3
- package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
- package/dist/codegen/registry.d.ts.map +1 -1
- package/dist/components/auto-release.d.ts +16 -0
- package/dist/components/auto-release.d.ts.map +1 -1
- package/dist/components/capability-plugin-loader.d.ts.map +1 -1
- package/dist/components/capability.d.ts +53 -0
- package/dist/components/capability.d.ts.map +1 -1
- package/dist/components/changed-files.d.ts +31 -0
- package/dist/components/changed-files.d.ts.map +1 -0
- package/dist/components/cli-support.d.ts +9 -1
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/component.d.ts +24 -0
- package/dist/components/component.d.ts.map +1 -1
- package/dist/components/deploy-units.d.ts.map +1 -1
- package/dist/components/driver.d.ts +13 -0
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/fan-out-output.d.ts +6 -0
- package/dist/components/fan-out-output.d.ts.map +1 -1
- package/dist/components/fan-out-record.d.ts +41 -0
- package/dist/components/fan-out-record.d.ts.map +1 -0
- package/dist/components/fan-out-run.d.ts +65 -0
- package/dist/components/fan-out-run.d.ts.map +1 -1
- package/dist/components/fan-out-support.d.ts +2 -0
- package/dist/components/fan-out-support.d.ts.map +1 -1
- package/dist/components/fan-out.d.ts +6 -0
- package/dist/components/fan-out.d.ts.map +1 -1
- package/dist/components/gated-wave-pipeline.d.ts +50 -0
- package/dist/components/gated-wave-pipeline.d.ts.map +1 -0
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/layers.d.ts +25 -0
- package/dist/components/layers.d.ts.map +1 -0
- package/dist/components/pr-pipeline.d.ts +61 -0
- package/dist/components/pr-pipeline.d.ts.map +1 -0
- package/dist/components/pr-run.d.ts +119 -0
- package/dist/components/pr-run.d.ts.map +1 -0
- package/dist/components/promote.d.ts.map +1 -1
- package/dist/components/starter-plugin.d.ts.map +1 -1
- package/dist/components/verbs/process-runner.d.ts.map +1 -1
- package/dist/components/verbs/r2-sync.d.ts.map +1 -1
- package/dist/components/verbs/run-agent.d.ts.map +1 -1
- package/dist/components/verbs/sbom-generator.d.ts.map +1 -1
- package/dist/components/verbs/shell.d.ts.map +1 -1
- package/dist/components/verbs/sign.d.ts.map +1 -1
- package/dist/components/verbs/verify.d.ts.map +1 -1
- package/dist/components/verbs/vuln-gate.d.ts.map +1 -1
- package/dist/components/verbs/vuln-scan.d.ts.map +1 -1
- package/dist/components/verbs/wrangler.d.ts.map +1 -1
- package/dist/components/wave-plan.d.ts +49 -0
- package/dist/components/wave-plan.d.ts.map +1 -0
- package/dist/composite.d.ts.map +1 -1
- package/dist/config.d.ts +19 -7
- package/dist/config.d.ts.map +1 -1
- package/dist/declarable.d.ts +16 -0
- package/dist/declarable.d.ts.map +1 -1
- package/dist/discovery/cache.d.ts.map +1 -1
- package/dist/discovery/fold-import.d.ts +15 -5
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/param-deps.d.ts +16 -0
- package/dist/discovery/param-deps.d.ts.map +1 -1
- package/dist/discovery/sandbox/policy-wire.d.ts.map +1 -1
- package/dist/discovery/specifier-resolve.d.ts +37 -0
- package/dist/discovery/specifier-resolve.d.ts.map +1 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/fold/fold.d.ts +3 -1
- package/dist/fold/fold.d.ts.map +1 -1
- package/dist/gated-waves.d.ts +147 -0
- package/dist/gated-waves.d.ts.map +1 -0
- package/dist/graph-detail.d.ts.map +1 -1
- package/dist/graph-ir.d.ts.map +1 -1
- package/dist/graph-layout.d.ts.map +1 -1
- package/dist/import/base-parser.d.ts.map +1 -1
- package/dist/import/embedded.d.ts +7 -0
- package/dist/import/embedded.d.ts.map +1 -1
- package/dist/import/parser-options.d.ts +14 -0
- package/dist/import/parser-options.d.ts.map +1 -0
- package/dist/import/parser.d.ts +17 -0
- package/dist/import/parser.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/kubectl-context.d.ts.map +1 -1
- package/dist/lexicon-output.d.ts.map +1 -1
- package/dist/lexicon-schema.d.ts +3 -3
- package/dist/lexicon.d.ts +103 -3
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/assert-live.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.d.ts +22 -0
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/git.d.ts +46 -1
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/member-ledger.d.ts.map +1 -1
- package/dist/lifecycle/observe.d.ts.map +1 -1
- package/dist/lifecycle/plan-ledger.d.ts.map +1 -1
- package/dist/lifecycle/release-ledger.d.ts.map +1 -1
- package/dist/lifecycle/scenario.d.ts.map +1 -1
- package/dist/lifecycle/status.d.ts.map +1 -1
- package/dist/lifecycle/work-lease.d.ts +61 -2
- package/dist/lifecycle/work-lease.d.ts.map +1 -1
- package/dist/lint/config.d.ts +8 -8
- package/dist/lint/engine.d.ts +6 -0
- package/dist/lint/engine.d.ts.map +1 -1
- package/dist/lint/post-synth.d.ts +18 -0
- package/dist/lint/post-synth.d.ts.map +1 -1
- package/dist/lsp/lexicon-providers.d.ts.map +1 -1
- package/dist/op/activities/activity-contracts.d.ts +137 -11
- package/dist/op/activities/activity-contracts.d.ts.map +1 -1
- package/dist/op/activities/apply.d.ts +79 -9
- package/dist/op/activities/apply.d.ts.map +1 -1
- package/dist/op/activities/change-set.d.ts +62 -0
- package/dist/op/activities/change-set.d.ts.map +1 -0
- package/dist/op/activities/decide.d.ts +2 -0
- package/dist/op/activities/decide.d.ts.map +1 -1
- package/dist/op/activities/factory.d.ts +137 -0
- package/dist/op/activities/factory.d.ts.map +1 -0
- package/dist/op/activities/index.d.ts +5 -1
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activities/lexicon-upgrade.d.ts +44 -12
- package/dist/op/activities/lexicon-upgrade.d.ts.map +1 -1
- package/dist/op/activity-contract.d.ts +1 -1
- package/dist/op/activity-contract.d.ts.map +1 -1
- package/dist/op/activity-failure.d.ts.map +1 -1
- package/dist/op/activity-profiles.d.ts.map +1 -1
- package/dist/op/builders.d.ts +11 -0
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/composites/apply-op.d.ts +12 -5
- package/dist/op/composites/apply-op.d.ts.map +1 -1
- package/dist/op/composites/lexicon-upgrade-op.d.ts +22 -6
- package/dist/op/composites/lexicon-upgrade-op.d.ts.map +1 -1
- package/dist/op/decide-backend.d.ts +5 -1
- package/dist/op/decide-backend.d.ts.map +1 -1
- package/dist/op/decide-read-inputs.d.ts +4 -2
- package/dist/op/decide-read-inputs.d.ts.map +1 -1
- package/dist/op/emulator-lifecycle.d.ts +21 -2
- package/dist/op/emulator-lifecycle.d.ts.map +1 -1
- package/dist/op/factory.d.ts +83 -0
- package/dist/op/factory.d.ts.map +1 -0
- package/dist/op/gate-approval.d.ts +5 -1
- package/dist/op/gate-approval.d.ts.map +1 -1
- package/dist/op/gate-wait.d.ts.map +1 -1
- package/dist/op/gate.d.ts +19 -3
- package/dist/op/gate.d.ts.map +1 -1
- package/dist/op/index.d.ts +6 -1
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/local-executor.d.ts +6 -0
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/op-ir.d.ts +2 -0
- package/dist/op/op-ir.d.ts.map +1 -1
- package/dist/op/op-verb-class.d.ts.map +1 -1
- package/dist/op/operator.d.ts.map +1 -1
- package/dist/op/run-context.d.ts +39 -0
- package/dist/op/run-context.d.ts.map +1 -0
- package/dist/op/run-live.d.ts.map +1 -1
- package/dist/op/runtimes/local.d.ts.map +1 -1
- package/dist/op/source-archive.d.ts.map +1 -1
- package/dist/op/steward-beside.d.ts.map +1 -1
- package/dist/op/steward-points.d.ts.map +1 -1
- package/dist/op/steward.d.ts.map +1 -1
- package/dist/op/types.d.ts +16 -3
- package/dist/op/types.d.ts.map +1 -1
- package/dist/op/work-lease-run.d.ts +24 -4
- package/dist/op/work-lease-run.d.ts.map +1 -1
- package/dist/plan-summary.d.ts +207 -0
- package/dist/plan-summary.d.ts.map +1 -0
- package/dist/pr-forge.d.ts +105 -0
- package/dist/pr-forge.d.ts.map +1 -0
- package/dist/pr-loop.d.ts +178 -0
- package/dist/pr-loop.d.ts.map +1 -0
- package/dist/project-root.d.ts.map +1 -1
- package/dist/provenance.d.ts +21 -0
- package/dist/provenance.d.ts.map +1 -1
- package/dist/pseudo-parameter.d.ts.map +1 -1
- package/dist/reconcile.d.ts.map +1 -1
- package/dist/secret-materialization.d.ts.map +1 -1
- package/dist/telemetry-attribution.d.ts +31 -0
- package/dist/telemetry-attribution.d.ts.map +1 -1
- package/dist/terraform/emit-source.d.ts.map +1 -1
- package/dist/terraform/hcl2json.d.ts +45 -0
- package/dist/terraform/hcl2json.d.ts.map +1 -0
- package/dist/terraform/parse.d.ts +4 -40
- package/dist/terraform/parse.d.ts.map +1 -1
- package/dist/testing.d.ts.map +1 -1
- package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -1
- package/dist/workspace/__fixtures__/decide-workspace.d.ts +1 -1
- package/dist/workspace/__fixtures__/decide-workspace.d.ts.map +1 -1
- package/dist/workspace/agent-cli.d.ts +5 -0
- package/dist/workspace/agent-cli.d.ts.map +1 -1
- package/dist/workspace/box-cli.d.ts +12 -0
- package/dist/workspace/box-cli.d.ts.map +1 -0
- package/dist/workspace/box-factory.d.ts +97 -0
- package/dist/workspace/box-factory.d.ts.map +1 -0
- package/dist/workspace/box-listing.d.ts +85 -0
- package/dist/workspace/box-listing.d.ts.map +1 -0
- package/dist/workspace/box-publish.d.ts +147 -0
- package/dist/workspace/box-publish.d.ts.map +1 -0
- package/dist/workspace/broker-protocol.d.ts +190 -0
- package/dist/workspace/broker-protocol.d.ts.map +1 -0
- package/dist/workspace/changes.d.ts.map +1 -1
- package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts.map +1 -1
- package/dist/workspace/checks/boxes.d.ts.map +1 -1
- package/dist/workspace/checks.d.ts +5 -0
- package/dist/workspace/checks.d.ts.map +1 -1
- package/dist/workspace/compose-graph.d.ts +10 -0
- package/dist/workspace/compose-graph.d.ts.map +1 -1
- package/dist/workspace/composites.d.ts +2 -2
- package/dist/workspace/composites.d.ts.map +1 -1
- package/dist/workspace/conformance/__fixtures__/reference-broker.d.ts +17 -0
- package/dist/workspace/conformance/__fixtures__/reference-broker.d.ts.map +1 -0
- package/dist/workspace/conformance/broker.d.ts +202 -0
- package/dist/workspace/conformance/broker.d.ts.map +1 -0
- package/dist/workspace/conformance/index.d.ts +68 -6
- package/dist/workspace/conformance/index.d.ts.map +1 -1
- package/dist/workspace/conformance/vitest.d.ts +19 -3
- package/dist/workspace/conformance/vitest.d.ts.map +1 -1
- package/dist/workspace/conformance/writer.d.ts +462 -0
- package/dist/workspace/conformance/writer.d.ts.map +1 -0
- package/dist/workspace/decide.d.ts +126 -12
- package/dist/workspace/decide.d.ts.map +1 -1
- package/dist/workspace/decision-points.schema.json +11 -4
- package/dist/workspace/declaration.d.ts +156 -4
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.schema.json +383 -5
- package/dist/workspace/environments.d.ts +36 -10
- package/dist/workspace/environments.d.ts.map +1 -1
- package/dist/workspace/factory-rules.d.ts +180 -0
- package/dist/workspace/factory-rules.d.ts.map +1 -0
- package/dist/workspace/generated-files.d.ts.map +1 -1
- package/dist/workspace/graph-cache.d.ts.map +1 -1
- package/dist/workspace/graph-cli.d.ts +2 -0
- package/dist/workspace/graph-cli.d.ts.map +1 -1
- package/dist/workspace/identity.d.ts +111 -0
- package/dist/workspace/identity.d.ts.map +1 -0
- package/dist/workspace/init.d.ts +21 -0
- package/dist/workspace/init.d.ts.map +1 -1
- package/dist/workspace/intent-cli.d.ts +7 -4
- package/dist/workspace/intent-cli.d.ts.map +1 -1
- package/dist/workspace/intent-record.d.ts +36 -2
- package/dist/workspace/intent-record.d.ts.map +1 -1
- package/dist/workspace/intent.d.ts +249 -8
- package/dist/workspace/intent.d.ts.map +1 -1
- package/dist/workspace/json-edit.d.ts +24 -0
- package/dist/workspace/json-edit.d.ts.map +1 -0
- package/dist/workspace/jsonc.d.ts +11 -0
- package/dist/workspace/jsonc.d.ts.map +1 -1
- package/dist/workspace/kinds.d.ts +66 -4
- package/dist/workspace/kinds.d.ts.map +1 -1
- package/dist/workspace/lexicon-symbols.d.ts +29 -0
- package/dist/workspace/lexicon-symbols.d.ts.map +1 -0
- package/dist/workspace/lineage-hash-index.d.ts.map +1 -1
- package/dist/workspace/lineage-lock.d.ts +2 -2
- package/dist/workspace/lineage-lock.d.ts.map +1 -1
- package/dist/workspace/lineage-upgrade.d.ts +31 -2
- package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
- package/dist/workspace/links.d.ts.map +1 -1
- package/dist/workspace/point-answer.schema.json +42 -2
- package/dist/workspace/points-cli.d.ts +10 -3
- package/dist/workspace/points-cli.d.ts.map +1 -1
- package/dist/workspace/points.d.ts +66 -5
- package/dist/workspace/points.d.ts.map +1 -1
- package/dist/workspace/principal-classes.d.ts +114 -0
- package/dist/workspace/principal-classes.d.ts.map +1 -0
- package/dist/workspace/reason-codes.d.ts +53 -4
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/record-assets.d.ts.map +1 -1
- package/dist/workspace/record-sessions.d.ts +5 -7
- package/dist/workspace/record-sessions.d.ts.map +1 -1
- package/dist/workspace/records-checkout.d.ts +54 -0
- package/dist/workspace/records-checkout.d.ts.map +1 -0
- package/dist/workspace/records-cli.d.ts +34 -1
- package/dist/workspace/records-cli.d.ts.map +1 -1
- package/dist/workspace/records-close.d.ts +7 -3
- package/dist/workspace/records-close.d.ts.map +1 -1
- package/dist/workspace/records-since.d.ts.map +1 -1
- package/dist/workspace/records-write.d.ts +68 -7
- package/dist/workspace/records-write.d.ts.map +1 -1
- package/dist/workspace/records.d.ts +43 -9
- package/dist/workspace/records.d.ts.map +1 -1
- package/dist/workspace/runs-cli.d.ts +296 -0
- package/dist/workspace/runs-cli.d.ts.map +1 -0
- package/dist/workspace/runs.d.ts +544 -0
- package/dist/workspace/runs.d.ts.map +1 -0
- package/dist/workspace/session-kinds.d.ts +1 -1
- package/dist/workspace/session-kinds.d.ts.map +1 -1
- package/dist/workspace/source-block.d.ts +1 -1
- package/dist/workspace/source-block.d.ts.map +1 -1
- package/dist/workspace/squash.d.ts +80 -0
- package/dist/workspace/squash.d.ts.map +1 -0
- package/dist/workspace/status-gates.d.ts +17 -3
- package/dist/workspace/status-gates.d.ts.map +1 -1
- package/dist/workspace/status.d.ts +64 -0
- package/dist/workspace/status.d.ts.map +1 -1
- package/dist/workspace/symbols.d.ts +63 -0
- package/dist/workspace/symbols.d.ts.map +1 -0
- package/dist/workspace/trailer-joins.d.ts +46 -0
- package/dist/workspace/trailer-joins.d.ts.map +1 -0
- package/dist/workspace/trailers.d.ts +89 -0
- package/dist/workspace/trailers.d.ts.map +1 -0
- package/dist/workspace/trust/attestor.d.ts.map +1 -1
- package/dist/workspace/trust/rotation.d.ts.map +1 -1
- package/dist/workspace/trust/run-commits.d.ts +46 -0
- package/dist/workspace/trust/run-commits.d.ts.map +1 -0
- package/dist/workspace/trust/run-statement.d.ts +128 -0
- package/dist/workspace/trust/run-statement.d.ts.map +1 -0
- package/dist/workspace/trust/seal.d.ts +38 -0
- package/dist/workspace/trust/seal.d.ts.map +1 -1
- package/dist/workspace/trust/test-repo.d.ts.map +1 -1
- package/dist/workspace/trust/verify-cli.d.ts +1 -1
- package/dist/workspace/trust/verify.d.ts +16 -2
- package/dist/workspace/trust/verify.d.ts.map +1 -1
- package/dist/workspace/wip-cli.d.ts +21 -0
- package/dist/workspace/wip-cli.d.ts.map +1 -0
- package/dist/workspace/wip.d.ts +268 -0
- package/dist/workspace/wip.d.ts.map +1 -0
- package/dist/workspace/work-cli.d.ts +39 -3
- package/dist/workspace/work-cli.d.ts.map +1 -1
- package/dist/workspace/work-evidence.d.ts +1 -1
- package/dist/workspace/work-evidence.d.ts.map +1 -1
- package/dist/workspace/work-size.d.ts +55 -0
- package/dist/workspace/work-size.d.ts.map +1 -0
- package/dist/workspace/work.d.ts +26 -1
- package/dist/workspace/work.d.ts.map +1 -1
- package/dist/workspace/workspace-kinds.schema.json +122 -0
- package/dist/workspace/workspace-principals.schema.json +60 -0
- package/dist/workspace/write-lock-cli.d.ts +60 -0
- package/dist/workspace/write-lock-cli.d.ts.map +1 -0
- package/dist/workspace/write-lock.d.ts +152 -0
- package/dist/workspace/write-lock.d.ts.map +1 -0
- package/dist/workspace/write-scope.d.ts +83 -19
- package/dist/workspace/write-scope.d.ts.map +1 -1
- package/dist/yaml.d.ts +21 -39
- package/dist/yaml.d.ts.map +1 -1
- package/package.json +7 -2
- package/src/__fixtures__/change-set/lifecycle-plan.floci.json +59 -0
- package/src/__fixtures__/change-set/lifecycle-v1.ts.txt +4 -0
- package/src/__fixtures__/change-set/lifecycle-v2.ts.txt +4 -0
- package/src/__fixtures__/change-set/lifecycle.change-set.golden.json +95 -0
- package/src/__fixtures__/change-set/warden.change-set.golden.json +106 -0
- package/src/__fixtures__/change-set/warden.plan.json +44 -0
- package/src/__fixtures__/plan-summary/normalization-vectors.json +1593 -0
- package/src/__fixtures__/pr-loop/pr-apply-applied.note.golden.md +31 -0
- package/src/__fixtures__/pr-loop/pr-apply-refused.note.golden.md +31 -0
- package/src/__fixtures__/pr-loop/pr-plan.golden.json +127 -0
- package/src/__fixtures__/pr-loop/pr-plan.note.golden.md +37 -0
- package/src/audit/core.test.ts +15 -0
- package/src/audit/core.ts +8 -2
- package/src/audit/discover.test.ts +11 -0
- package/src/audit/discover.ts +7 -2
- package/src/audit/prior-art.ts +15 -0
- package/src/change-set-bundle.test.ts +57 -0
- package/src/change-set.test.ts +215 -0
- package/src/change-set.ts +445 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +1 -1
- package/src/cli/commands/audit-opt-in.test.ts +54 -0
- package/src/cli/commands/audit.ts +29 -2
- package/src/cli/commands/build.test.ts +19 -0
- package/src/cli/commands/build.ts +3 -2
- package/src/cli/commands/carve-emit-state.test.ts +4 -0
- package/src/cli/commands/carve-emit.ts +5 -19
- package/src/cli/commands/check-lexicon.test.ts +62 -1
- package/src/cli/commands/check-lexicon.ts +36 -6
- package/src/cli/commands/import-live.test.ts +18 -0
- package/src/cli/commands/import-parser-options.test.ts +133 -0
- package/src/cli/commands/import.ts +21 -3
- package/src/cli/commands/init-lexicon/templates/project.ts +1 -1
- package/src/cli/commands/init.test.ts +8 -1
- package/src/cli/commands/init.ts +23 -8
- package/src/cli/commands/lint.test.ts +37 -0
- package/src/cli/commands/onboard.test.ts +53 -3
- package/src/cli/commands/onboard.ts +52 -4
- package/src/cli/commands/pinned-upgrade.ts +73 -33
- package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +2 -2
- package/src/cli/handlers/build.ts +13 -1
- package/src/cli/handlers/change-set.test.ts +56 -0
- package/src/cli/handlers/change-set.ts +44 -0
- package/src/cli/handlers/components.ts +2 -2
- package/src/cli/handlers/dev.ts +8 -6
- package/src/cli/handlers/fan-out.test.ts +221 -2
- package/src/cli/handlers/fan-out.ts +165 -75
- package/src/cli/handlers/graph.ts +3 -0
- package/src/cli/handlers/lifecycle.test.ts +67 -0
- package/src/cli/handlers/lifecycle.ts +6 -5
- package/src/cli/handlers/misc.ts +2 -0
- package/src/cli/handlers/operator.test.ts +21 -0
- package/src/cli/handlers/operator.ts +121 -6
- package/src/cli/handlers/pr.ts +373 -0
- package/src/cli/handlers/run.test.ts +111 -0
- package/src/cli/handlers/run.ts +86 -24
- package/src/cli/handlers/scenario.ts +2 -2
- package/src/cli/main.test.ts +21 -0
- package/src/cli/main.ts +246 -30
- package/src/cli/mcp/never-over-mcp.test.ts +101 -0
- package/src/cli/mcp/op-tools.ts +24 -2
- package/src/cli/mcp/workspace-tools.test.ts +5 -2
- package/src/cli/mcp/workspace-tools.ts +62 -7
- package/src/cli/registry.ts +74 -3
- package/src/codegen/pinned-upgrade-multi.test.ts +157 -0
- package/src/codegen/pinned-upgrade-tags.test.ts +112 -0
- package/src/codegen/pinned-upgrade.test.ts +104 -0
- package/src/codegen/pinned-upgrade.ts +250 -56
- package/src/codegen/testdata/postgres-tags.json +242 -0
- package/src/components/auto-release.ts +1 -1
- package/src/components/capability.ts +55 -0
- package/src/components/changed-files.ts +66 -0
- package/src/components/cli-support.ts +28 -5
- package/src/components/component.schema.json +17 -0
- package/src/components/component.ts +25 -0
- package/src/components/deploy-units.ts +3 -0
- package/src/components/driver.test.ts +52 -0
- package/src/components/driver.ts +31 -13
- package/src/components/fan-out-output.ts +42 -1
- package/src/components/fan-out-record.ts +69 -0
- package/src/components/fan-out-run.test.ts +17 -0
- package/src/components/fan-out-run.ts +304 -1
- package/src/components/fan-out-support.ts +3 -0
- package/src/components/fan-out-waves.test.ts +375 -0
- package/src/components/fan-out.ts +18 -17
- package/src/components/gated-wave-pipeline.ts +82 -0
- package/src/components/index.ts +1 -0
- package/src/components/layers.ts +30 -0
- package/src/components/pr-pipeline.ts +91 -0
- package/src/components/pr-run.test.ts +205 -0
- package/src/components/pr-run.ts +280 -0
- package/src/components/promote.ts +5 -0
- package/src/components/verbs/shell.test.ts +11 -0
- package/src/components/verbs/shell.ts +7 -3
- package/src/components/wave-plan.ts +124 -0
- package/src/config.test.ts +21 -0
- package/src/config.ts +15 -0
- package/src/declarable.test.ts +21 -0
- package/src/declarable.ts +19 -0
- package/src/discovery/fold-composite.test.ts +41 -5
- package/src/discovery/fold-import.test.ts +129 -0
- package/src/discovery/fold-import.ts +643 -131
- package/src/discovery/fold-package-composite.test.ts +264 -0
- package/src/discovery/fold-specifiers.test.ts +191 -0
- package/src/discovery/param-deps.test.ts +66 -1
- package/src/discovery/param-deps.ts +45 -0
- package/src/discovery/sandbox/bundle.ts +1 -1
- package/src/discovery/specifier-resolve.ts +344 -0
- package/src/fold/fold.test.ts +39 -1
- package/src/fold/fold.ts +83 -9
- package/src/fold-provenance.test.ts +140 -0
- package/src/gated-waves-bundle.test.ts +62 -0
- package/src/gated-waves.test.ts +118 -0
- package/src/gated-waves.ts +240 -0
- package/src/graph-ir.test.ts +83 -0
- package/src/graph-ir.ts +29 -0
- package/src/import/embedded.test.ts +20 -0
- package/src/import/embedded.ts +13 -0
- package/src/import/parser-options.ts +46 -0
- package/src/import/parser.ts +20 -0
- package/src/index.ts +1 -0
- package/src/lexicon.ts +106 -3
- package/src/lifecycle/gate-ledger.ts +23 -0
- package/src/lifecycle/git.ts +102 -16
- package/src/lifecycle/observe.test.ts +18 -0
- package/src/lifecycle/observe.ts +6 -4
- package/src/lifecycle/snapshot.ts +2 -2
- package/src/lifecycle/work-lease.test.ts +1 -1
- package/src/lifecycle/work-lease.ts +93 -2
- package/src/lint/engine.test.ts +55 -0
- package/src/lint/engine.ts +30 -4
- package/src/lint/output-docs.test.ts +59 -56
- package/src/lint/post-synth.test.ts +19 -0
- package/src/lint/post-synth.ts +19 -1
- package/src/meta/declared-imports.test.ts +1 -1
- package/src/op/activities/activity-contracts.ts +84 -0
- package/src/op/activities/apply.test.ts +189 -5
- package/src/op/activities/apply.ts +230 -8
- package/src/op/activities/change-set.ts +114 -0
- package/src/op/activities/decide.ts +3 -0
- package/src/op/activities/factory.ts +675 -0
- package/src/op/activities/index.ts +9 -0
- package/src/op/activities/lexicon-upgrade.test.ts +160 -6
- package/src/op/activities/lexicon-upgrade.ts +154 -51
- package/src/op/activity-contract.test.ts +21 -1
- package/src/op/activity-contract.ts +19 -9
- package/src/op/builders.ts +23 -0
- package/src/op/composites/apply-op.ts +12 -5
- package/src/op/composites/composites.test.ts +30 -3
- package/src/op/composites/lexicon-upgrade-op.test.ts +7 -2
- package/src/op/composites/lexicon-upgrade-op.ts +29 -8
- package/src/op/decide-backend.ts +5 -1
- package/src/op/decide-read-inputs.ts +33 -5
- package/src/op/emulator-lifecycle.test.ts +23 -0
- package/src/op/emulator-lifecycle.ts +52 -14
- package/src/op/factory.e2e.test.ts +232 -0
- package/src/op/factory.ts +118 -0
- package/src/op/gate-approval.test.ts +74 -0
- package/src/op/gate-approval.ts +5 -1
- package/src/op/gate.ts +31 -5
- package/src/op/index.ts +11 -1
- package/src/op/local-executor.test.ts +15 -0
- package/src/op/local-executor.ts +40 -12
- package/src/op/local-output.ts +1 -1
- package/src/op/op-ir.test.ts +9 -0
- package/src/op/op-ir.ts +4 -0
- package/src/op/op-verb-class.ts +3 -0
- package/src/op/operator.ts +4 -0
- package/src/op/run-context.test.ts +141 -0
- package/src/op/run-context.ts +51 -0
- package/src/op/runtimes/local.test.ts +14 -0
- package/src/op/runtimes/local.ts +2 -0
- package/src/op/steward-beside.ts +2 -0
- package/src/op/types.ts +16 -3
- package/src/op/work-lease-run.test.ts +47 -0
- package/src/op/work-lease-run.ts +76 -11
- package/src/plan-summary.test.ts +263 -0
- package/src/plan-summary.ts +845 -0
- package/src/pr-forge.test.ts +237 -0
- package/src/pr-forge.ts +332 -0
- package/src/pr-loop-bundle.test.ts +46 -0
- package/src/pr-loop.test.ts +171 -0
- package/src/pr-loop.ts +324 -0
- package/src/provenance.ts +35 -0
- package/src/telemetry-attribution.test.ts +23 -1
- package/src/telemetry-attribution.ts +51 -0
- package/src/terraform/hcl2json.test.ts +21 -0
- package/src/terraform/hcl2json.ts +81 -0
- package/src/terraform/parse.ts +11 -74
- package/src/workspace/agent-cli.ts +7 -3
- package/src/workspace/agent.schema.json +22 -0
- package/src/workspace/app-kind.test.ts +196 -0
- package/src/workspace/box-cli.ts +113 -0
- package/src/workspace/box-factory.test.ts +323 -0
- package/src/workspace/box-factory.ts +136 -0
- package/src/workspace/box-listing-write.schema.json +204 -0
- package/src/workspace/box-listing.test.ts +234 -0
- package/src/workspace/box-listing.ts +324 -0
- package/src/workspace/box-publish.schema.json +498 -0
- package/src/workspace/box-publish.test.ts +204 -0
- package/src/workspace/box-publish.ts +420 -0
- package/src/workspace/box-ship.test.ts +96 -0
- package/src/workspace/broker-protocol.schema.json +186 -0
- package/src/workspace/broker-protocol.test.ts +122 -0
- package/src/workspace/broker-protocol.ts +317 -0
- package/src/workspace/builder-tiers.test.ts +146 -0
- package/src/workspace/change-set.schema.json +460 -0
- package/src/workspace/changes.schema.json +12 -20
- package/src/workspace/changes.ts +4 -1
- package/src/workspace/chant-migrations.e2e.test.ts +17 -5
- package/src/workspace/checks/boxes.test.ts +5 -0
- package/src/workspace/checks.ts +39 -8
- package/src/workspace/component-environments.test.ts +105 -0
- package/src/workspace/compose-graph.ts +7 -0
- package/src/workspace/composites.schema.json +23 -4
- package/src/workspace/composites.test.ts +8 -8
- package/src/workspace/composites.ts +13 -2
- package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +2 -2
- package/src/workspace/conformance/__fixtures__/reference-broker.ts +207 -0
- package/src/workspace/conformance/__writer_fixture__/answers/answer.kind.mjs +32 -0
- package/src/workspace/conformance/__writer_fixture__/answers/answer.schema.json +135 -0
- package/src/workspace/conformance/__writer_fixture__/contracts/contract.kind.mjs +37 -0
- package/src/workspace/conformance/__writer_fixture__/contracts/contract.schema.json +213 -0
- package/src/workspace/conformance/__writer_fixture__/decisions/points.json +228 -0
- package/src/workspace/conformance/__writer_fixture__/drivers/driver.kind.mjs +15 -0
- package/src/workspace/conformance/__writer_fixture__/drivers/driver.schema.json +38 -0
- package/src/workspace/conformance/__writer_fixture__/sessions/session.kind.mjs +54 -0
- package/src/workspace/conformance/__writer_fixture__/sessions/session.schema.json +289 -0
- package/src/workspace/conformance/__writer_fixture__/work/W-001-the-writer-suite-works-this-item.md +25 -0
- package/src/workspace/conformance/__writer_fixture__/work/work.kind.mjs +60 -0
- package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +603 -0
- package/src/workspace/conformance/broker.test.ts +122 -0
- package/src/workspace/conformance/broker.ts +792 -0
- package/src/workspace/conformance/conformance.e2e.test.ts +36 -3
- package/src/workspace/conformance/index.mjs +51 -3
- package/src/workspace/conformance/index.ts +107 -15
- package/src/workspace/conformance/vitest.ts +76 -2
- package/src/workspace/conformance/writer-conformance.e2e.test.ts +177 -0
- package/src/workspace/conformance/writer.test.ts +186 -0
- package/src/workspace/conformance/writer.ts +1191 -0
- package/src/workspace/decide.test.ts +208 -4
- package/src/workspace/decide.ts +433 -73
- package/src/workspace/decision-points.schema.json +11 -4
- package/src/workspace/declaration.schema.json +383 -5
- package/src/workspace/declaration.ts +315 -10
- package/src/workspace/environments.ts +66 -14
- package/src/workspace/factory-rules.test.ts +62 -0
- package/src/workspace/factory-rules.ts +218 -0
- package/src/workspace/graph-cli.ts +6 -3
- package/src/workspace/graph.schema.json +314 -0
- package/src/workspace/identity.test.ts +219 -0
- package/src/workspace/identity.ts +186 -0
- package/src/workspace/init.ts +82 -2
- package/src/workspace/intent-cli.ts +84 -13
- package/src/workspace/intent-patch-id.test.ts +162 -0
- package/src/workspace/intent-record.schema.json +336 -1
- package/src/workspace/intent-record.ts +96 -16
- package/src/workspace/intent-reviews.test.ts +76 -0
- package/src/workspace/intent-squash.test.ts +213 -0
- package/src/workspace/intent-why.test.ts +274 -0
- package/src/workspace/intent.schema.json +950 -9
- package/src/workspace/intent.test.ts +11 -2
- package/src/workspace/intent.ts +722 -65
- package/src/workspace/json-edit.test.ts +121 -0
- package/src/workspace/json-edit.ts +142 -0
- package/src/workspace/jsonc.ts +20 -0
- package/src/workspace/kinds.ts +201 -39
- package/src/workspace/lexicon-symbols.test.ts +148 -0
- package/src/workspace/lexicon-symbols.ts +125 -0
- package/src/workspace/lineage-upgrade.test.ts +63 -0
- package/src/workspace/lineage-upgrade.ts +146 -5
- package/src/workspace/plan-summary.schema.json +155 -0
- package/src/workspace/point-answer.schema.json +42 -2
- package/src/workspace/points-cli.ts +42 -16
- package/src/workspace/points-write.schema.json +163 -4
- package/src/workspace/points.schema.json +157 -2
- package/src/workspace/points.test.ts +13 -1
- package/src/workspace/points.ts +118 -12
- package/src/workspace/pr-report.schema.json +95 -0
- package/src/workspace/principal-classes.test.ts +175 -0
- package/src/workspace/principal-classes.ts +307 -0
- package/src/workspace/profiles.test.ts +159 -0
- package/src/workspace/read-contract.test.ts +2 -2
- package/src/workspace/reason-codes.test.ts +23 -3
- package/src/workspace/reason-codes.ts +61 -4
- package/src/workspace/record-sessions.ts +9 -8
- package/src/workspace/records-amend.schema.json +80 -0
- package/src/workspace/records-checkout.ts +106 -0
- package/src/workspace/records-cli.ts +123 -17
- package/src/workspace/records-close.schema.json +79 -0
- package/src/workspace/records-close.ts +31 -7
- package/src/workspace/records-concurrent.e2e.test.ts +135 -0
- package/src/workspace/records-concurrent.test.ts +145 -0
- package/src/workspace/records-formats.test.ts +46 -10
- package/src/workspace/records-new.schema.json +9 -0
- package/src/workspace/records-review.schema.json +80 -0
- package/src/workspace/records-sessions-write.test.ts +69 -0
- package/src/workspace/records-sessions.test.ts +22 -0
- package/src/workspace/records-uncommitted.test.ts +171 -0
- package/src/workspace/records-write.ts +219 -30
- package/src/workspace/records.schema.json +88 -1
- package/src/workspace/records.ts +76 -8
- package/src/workspace/reference-kinds/app/package.json +10 -0
- package/src/workspace/reference-kinds/app/workspace-kinds.json +45 -0
- package/src/workspace/run-statement.schema.json +622 -0
- package/src/workspace/runs-cli.ts +710 -0
- package/src/workspace/runs-statement.test.ts +239 -0
- package/src/workspace/runs-write.schema.json +994 -0
- package/src/workspace/runs.schema.json +1257 -0
- package/src/workspace/runs.test.ts +281 -0
- package/src/workspace/runs.ts +970 -0
- package/src/workspace/session-kinds.ts +3 -3
- package/src/workspace/squash.ts +198 -0
- package/src/workspace/status-contract.test.ts +10 -2
- package/src/workspace/status-gates.ts +25 -9
- package/src/workspace/status.schema.json +252 -4
- package/src/workspace/status.ts +110 -1
- package/src/workspace/symbols.ts +191 -0
- package/src/workspace/trailer-joins.test.ts +171 -0
- package/src/workspace/trailer-joins.ts +82 -0
- package/src/workspace/trailers.ts +148 -0
- package/src/workspace/trust/run-commits.ts +115 -0
- package/src/workspace/trust/run-statement.ts +226 -0
- package/src/workspace/trust/seal.ts +58 -1
- package/src/workspace/trust/verify-cli.ts +6 -5
- package/src/workspace/trust/verify.ts +39 -6
- package/src/workspace/wip-cli.ts +97 -0
- package/src/workspace/wip-write.schema.json +504 -0
- package/src/workspace/wip.schema.json +334 -0
- package/src/workspace/wip.test.ts +294 -0
- package/src/workspace/wip.ts +753 -0
- package/src/workspace/work-cli.e2e.test.ts +28 -1
- package/src/workspace/work-cli.ts +136 -8
- package/src/workspace/work-evidence.schema.json +5 -0
- package/src/workspace/work-history.schema.json +73 -1
- package/src/workspace/work-history.test.ts +48 -5
- package/src/workspace/work-kind-fields.test.ts +154 -0
- package/src/workspace/work-lease.schema.json +14 -1
- package/src/workspace/work-size.test.ts +77 -0
- package/src/workspace/work-size.ts +93 -0
- package/src/workspace/work.ts +82 -0
- package/src/workspace/workspace-kinds.schema.json +122 -0
- package/src/workspace/workspace-principals.schema.json +60 -0
- package/src/workspace/write-lock-cli.ts +131 -0
- package/src/workspace/write-lock.schema.json +172 -0
- package/src/workspace/write-lock.test.ts +189 -0
- package/src/workspace/write-lock.ts +474 -0
- package/src/workspace/write-scope.test.ts +144 -3
- package/src/workspace/write-scope.ts +192 -45
- package/src/yaml.test.ts +111 -1
- package/src/yaml.ts +141 -549
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The factory reference Op (#3406, ws-087), run through the local executor
|
|
3
|
+
* with stub hooks on a small workspace that carries the reference
|
|
4
|
+
* workspace's decision, work and answer kinds and its points: one item from
|
|
5
|
+
* pick to done, one retry after a failed build, and one ask the understand
|
|
6
|
+
* point refuses. The rules themselves are tested in
|
|
7
|
+
* ../workspace/factory-rules.test.ts.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { spawnSync } from "node:child_process";
|
|
11
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { describe, expect, test } from "vitest";
|
|
14
|
+
import { withTestDir } from "@intentius/chant-test-utils";
|
|
15
|
+
import * as coreActivities from "./activities";
|
|
16
|
+
import type { ActivityFn } from "./activity-registry";
|
|
17
|
+
import { ACTIVITY_PROFILES } from "./activity-profiles";
|
|
18
|
+
import { factoryOpConfig } from "./factory";
|
|
19
|
+
import { OpRunFailure, runOpLocally, type OpRunResult } from "./local-executor";
|
|
20
|
+
import { answerPoint } from "../workspace/decide";
|
|
21
|
+
import { workspacePoints } from "../workspace/points-cli";
|
|
22
|
+
import { amendRecord } from "../workspace/records-write";
|
|
23
|
+
import { parseFrontMatter } from "../workspace/records";
|
|
24
|
+
|
|
25
|
+
const REF = join(import.meta.dirname, "..", "..", "..", "..", "reference-workspace");
|
|
26
|
+
|
|
27
|
+
const activities = new Map<string, ActivityFn>(Object.entries(coreActivities).filter(([, v]) => typeof v === "function") as [string, ActivityFn][]);
|
|
28
|
+
|
|
29
|
+
function git(args: string[], cwd: string): string {
|
|
30
|
+
const r = spawnSync("git", args, { cwd, encoding: "utf-8" });
|
|
31
|
+
if (r.status !== 0) throw new Error(`git ${args.join(" ")}: ${r.stderr}`);
|
|
32
|
+
return r.stdout.trim();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const item = (id: string, fields: Record<string, unknown>, body = "") =>
|
|
36
|
+
`---\n${JSON.stringify({ schema: 1, id, title: `Item ${id}`, state: "open", implements: [], needs: [], constrains: ["member:app"], evidence: [], opened_on: "2026-10-01", source: { kind: "workspace", member: "app" }, supersedes: [], ...fields }, null, 2)}\n---\n\n# Item ${id}\n${body}`;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The builder stub: writes app/<item>.txt, and fails while .fail-<item> exists in the checkout
|
|
40
|
+
* the test controls (FACTORY_FAIL_DIR), so a retry can succeed.
|
|
41
|
+
*/
|
|
42
|
+
const BUILDER = `
|
|
43
|
+
const fs = require("node:fs");
|
|
44
|
+
const item = process.env.FACTORY_ITEM;
|
|
45
|
+
if (fs.existsSync(require("node:path").join(process.env.FACTORY_FAIL_DIR, ".fail-" + item))) { console.error("the build broke"); process.exit(1); }
|
|
46
|
+
fs.mkdirSync("app", { recursive: true });
|
|
47
|
+
fs.writeFileSync("app/" + item + ".txt", "built at tier " + process.env.FACTORY_TIER + "\\n");
|
|
48
|
+
`;
|
|
49
|
+
const CHECK = `const fs = require("node:fs"); process.exit(fs.existsSync("app/" + process.env.FACTORY_ITEM + ".txt") ? 0 : 1);`;
|
|
50
|
+
|
|
51
|
+
function workspace(dir: string): void {
|
|
52
|
+
git(["init", "-q", "-b", "main"], dir);
|
|
53
|
+
git(["config", "user.email", "t@chant.dev"], dir);
|
|
54
|
+
git(["config", "user.name", "T"], dir);
|
|
55
|
+
git(["config", "commit.gpgsign", "false"], dir);
|
|
56
|
+
for (const d of ["decisions", "work", "answers"]) mkdirSync(join(dir, d), { recursive: true });
|
|
57
|
+
for (const f of ["decisions/decision.kind.mjs", "decisions/decision.schema.json", "decisions/points.json", "work/work.kind.mjs", "work/work.schema.json", "answers/answer.kind.mjs", "answers/answer.schema.json"]) {
|
|
58
|
+
cpSync(join(REF, f), join(dir, f));
|
|
59
|
+
}
|
|
60
|
+
writeFileSync(
|
|
61
|
+
join(dir, "chant.workspace.json"),
|
|
62
|
+
JSON.stringify({
|
|
63
|
+
name: "factory",
|
|
64
|
+
schema: 1,
|
|
65
|
+
records: [{ kind: "decisions/decision.kind.mjs" }, { kind: "work/work.kind.mjs" }, { kind: "answers/answer.kind.mjs" }],
|
|
66
|
+
members: [{ name: "app", dir: "app", kind: "other", because: "the app" }],
|
|
67
|
+
}),
|
|
68
|
+
);
|
|
69
|
+
mkdirSync(join(dir, "app"), { recursive: true });
|
|
70
|
+
writeFileSync(join(dir, "app", "README.md"), "# app\n");
|
|
71
|
+
mkdirSync(join(dir, "hooks"), { recursive: true });
|
|
72
|
+
writeFileSync(join(dir, "hooks", "builder.cjs"), BUILDER);
|
|
73
|
+
writeFileSync(join(dir, "hooks", "check.cjs"), CHECK);
|
|
74
|
+
writeFileSync(join(dir, "work", "W-101-the-feature.md"), item("W-101", { acceptance: [{ id: "AC-1", text: "app/W-101.txt exists", verification: "unit" }] }));
|
|
75
|
+
writeFileSync(join(dir, "work", "W-102-the-retried-one.md"), item("W-102", { acceptance: [{ id: "AC-1", text: "app/W-102.txt exists", verification: "unit" }] }));
|
|
76
|
+
writeFileSync(join(dir, "work", "W-103-an-ask.md"), item("W-103", { state: "proposed", source: { ask: { said: "Make the page purple.", by: "alice", via: "hud" } } }));
|
|
77
|
+
git(["add", "-A"], dir);
|
|
78
|
+
git(["commit", "-q", "-m", "the workspace"], dir);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function run(dir: string, work?: string, extra: Partial<Parameters<typeof factoryOpConfig>[0]> = {}): Promise<OpRunResult> {
|
|
82
|
+
const config = factoryOpConfig({ cwd: dir, backends: { systemone: { url: "http://127.0.0.1:9", timeoutMs: 200 } }, builder: `${process.execPath} hooks/builder.cjs`, check: `${process.execPath} hooks/check.cjs`, ...extra });
|
|
83
|
+
const prev = process.env.FACTORY_FAIL_DIR;
|
|
84
|
+
process.env.FACTORY_FAIL_DIR = dir;
|
|
85
|
+
try {
|
|
86
|
+
return await runOpLocally(config, activities, ACTIVITY_PROFILES, undefined, { cwd: dir, ledger: { cwd: dir }, ...(work ? { work: { item: work } } : {}) });
|
|
87
|
+
} catch (err) {
|
|
88
|
+
if (err instanceof OpRunFailure) return err.result;
|
|
89
|
+
throw err;
|
|
90
|
+
} finally {
|
|
91
|
+
if (prev === undefined) delete process.env.FACTORY_FAIL_DIR;
|
|
92
|
+
else process.env.FACTORY_FAIL_DIR = prev;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const onBranch = (dir: string, id: string, path: string) => {
|
|
97
|
+
const fm = parseFrontMatter(git(["show", `chant/work/${id}:${path}`], dir));
|
|
98
|
+
if (!fm.ok) throw new Error(fm.message);
|
|
99
|
+
return fm.value;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
describe("the factory reference Op (#3406)", () => {
|
|
103
|
+
test("builds an item from pick to done, retries a failed build only on a person's request, and drops an ask understand refuses", async () => {
|
|
104
|
+
await withTestDir(async (dir) => {
|
|
105
|
+
workspace(dir);
|
|
106
|
+
writeFileSync(join(dir, ".fail-W-102"), "");
|
|
107
|
+
|
|
108
|
+
// W-101: picked first, tier from slice-tier's table, built, checked, recorded done on its branch.
|
|
109
|
+
const first = await run(dir);
|
|
110
|
+
expect(first.workLease).toMatchObject({ item: "W-101", released: true, outcome: "done" });
|
|
111
|
+
const w101 = onBranch(dir, "W-101", "work/W-101-the-feature.md");
|
|
112
|
+
expect(w101).toMatchObject({ state: "done", result: { lease: first.workLease!.token } });
|
|
113
|
+
expect(git(["show", "chant/work/W-101:app/W-101.txt"], dir)).toBe("built at tier small");
|
|
114
|
+
const msg = git(["log", "-1", "--format=%B", "chant/work/W-101"], dir);
|
|
115
|
+
expect(msg).toContain("Chant-Record: work:W-101");
|
|
116
|
+
expect(msg).toContain(`Chant-Lease: ${first.workLease!.token}`);
|
|
117
|
+
|
|
118
|
+
// W-101 waits to be applied, so W-102 is next; its builder fails, and the attempt is kept.
|
|
119
|
+
const second = await run(dir);
|
|
120
|
+
expect(second.workLease).toMatchObject({ item: "W-102", outcome: "not_done" });
|
|
121
|
+
const failed = second.workLease!.token!;
|
|
122
|
+
|
|
123
|
+
// Neither W-101 (built, not applied) nor W-102 (failed, no retry) is picked; W-103 is an ask,
|
|
124
|
+
// and understand escalates it to people, so the run waits on the question.
|
|
125
|
+
const third = await run(dir);
|
|
126
|
+
expect(third.workLease).toMatchObject({ item: "W-103", outcome: "waiting" });
|
|
127
|
+
|
|
128
|
+
// A person asks for W-102 again, naming the failed build; the next run builds it.
|
|
129
|
+
const retry = await amendRecord({ kind: join(dir, "work", "work.kind.mjs"), id: "W-102", fields: JSON.stringify({ retry: { after: failed, by: "alice", at: "2026-10-03T10:00:00Z" } }), cwd: dir });
|
|
130
|
+
expect("error" in retry).toBe(false);
|
|
131
|
+
git(["add", "-A"], dir);
|
|
132
|
+
git(["commit", "-q", "-m", "retry W-102"], dir);
|
|
133
|
+
unlinkSync(join(dir, ".fail-W-102"));
|
|
134
|
+
const fourth = await run(dir, "W-102");
|
|
135
|
+
expect(fourth.workLease).toMatchObject({ item: "W-102", outcome: "done" });
|
|
136
|
+
|
|
137
|
+
// The understand question about W-103 is answered refuse; the next run drops it on its branch.
|
|
138
|
+
const points = await workspacePoints({ cwd: dir });
|
|
139
|
+
if ("error" in points) throw new Error(points.error.message);
|
|
140
|
+
const open = points.questions.find((q) => q.point === "understand" && q.open);
|
|
141
|
+
expect(open).toBeDefined();
|
|
142
|
+
const answered = await answerPoint({ cwd: dir, id: open!.id, answer: "refuse", by: ["alice"] });
|
|
143
|
+
expect("error" in answered).toBe(false);
|
|
144
|
+
git(["add", "-A"], dir);
|
|
145
|
+
git(["commit", "-q", "-m", "refuse W-103"], dir);
|
|
146
|
+
const fifth = await run(dir, "W-103");
|
|
147
|
+
expect(fifth.workLease).toMatchObject({ item: "W-103", outcome: "dropped" });
|
|
148
|
+
expect(onBranch(dir, "W-103", "work/W-103-an-ask.md")).toMatchObject({ state: "dropped" });
|
|
149
|
+
expect(existsSync(join(dir, "app", "W-103.txt"))).toBe(false);
|
|
150
|
+
expect(readFileSync(join(dir, "work", "W-101-the-feature.md"), "utf-8")).toContain('"state": "open"');
|
|
151
|
+
});
|
|
152
|
+
}, 120_000);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe("the factory Op's seams for an orchestrator (studio#382)", () => {
|
|
156
|
+
test("prepare runs before Pick, an uncommitted item is carried into the worktree, the reports pass through, the check ticks each criterion by its own result, and after sees the outcome", async () => {
|
|
157
|
+
await withTestDir(async (dir) => {
|
|
158
|
+
workspace(dir);
|
|
159
|
+
// A builder that reports a run and a check that reports per criterion.
|
|
160
|
+
writeFileSync(join(dir, "hooks", "builder2.cjs"), `${BUILDER}\nconsole.log(JSON.stringify({ ok: true, run: { id: "run-7" }, agent: "builder-small", chantAgent: "factory", records: ["contract:C-1"] }));\n`);
|
|
161
|
+
writeFileSync(join(dir, "hooks", "check2.cjs"), `console.log(JSON.stringify({ criteria: { "AC-1": "pass", "AC-2": "fail" }, built: JSON.parse(process.env.FACTORY_BUILD).agent }));`);
|
|
162
|
+
writeFileSync(join(dir, "hooks", "after.cjs"), `require("node:fs").writeFileSync(require("node:path").join(process.env.FACTORY_FAIL_DIR, "after.json"), JSON.stringify({ outcome: process.env.FACTORY_OUTCOME, commit: process.env.FACTORY_COMMIT, reason: process.env.FACTORY_REASON, check: JSON.parse(process.env.FACTORY_CHECK) }));`);
|
|
163
|
+
// The prepare hook writes W-110 in the checkout and leaves it uncommitted; the others are taken out of the way.
|
|
164
|
+
writeFileSync(
|
|
165
|
+
join(dir, "hooks", "prepare.cjs"),
|
|
166
|
+
`require("node:fs").writeFileSync("work/W-110-prepared.md", ${JSON.stringify(item("W-110", { acceptance: [{ id: "AC-1", text: "one", verification: "unit" }, { id: "AC-2", text: "two", verification: "unit" }] }))});`,
|
|
167
|
+
);
|
|
168
|
+
for (const id of ["W-101", "W-102"]) writeFileSync(join(dir, ".fail-" + id), "");
|
|
169
|
+
git(["add", "-A"], dir);
|
|
170
|
+
git(["commit", "-q", "-m", "hooks"], dir);
|
|
171
|
+
const result = await run(dir, "W-110", {
|
|
172
|
+
kind: "./work/work.kind.mjs",
|
|
173
|
+
prepare: `${process.execPath} hooks/prepare.cjs`,
|
|
174
|
+
builder: `${process.execPath} hooks/builder2.cjs`,
|
|
175
|
+
check: `${process.execPath} hooks/check2.cjs`,
|
|
176
|
+
after: `${process.execPath} hooks/after.cjs`,
|
|
177
|
+
});
|
|
178
|
+
// AC-2 failed its own check, so the item is not done, though the check exited 0.
|
|
179
|
+
expect(result.workLease).toMatchObject({ item: "W-110", outcome: "not_done" });
|
|
180
|
+
const after = JSON.parse(readFileSync(join(dir, "after.json"), "utf-8"));
|
|
181
|
+
expect(after).toMatchObject({ outcome: "not_done", reason: "criteria AC-2 have no passing evidence", check: { built: "builder-small" } });
|
|
182
|
+
// The attempt was committed with why, and the run kept that commit.
|
|
183
|
+
expect(after.commit).toMatch(/^[0-9a-f]{40}$/);
|
|
184
|
+
// The kept attempt holds the carried item with AC-1 ticked and AC-2 failed.
|
|
185
|
+
const kept = git(["for-each-ref", "--format=%(refname)", "refs/chant/kept/"], dir).split("\n").find((r) => r.includes("W-110"))!;
|
|
186
|
+
expect(git(["rev-parse", kept], dir)).toBe(after.commit);
|
|
187
|
+
expect(git(["log", "-1", "--format=%B", kept], dir)).toMatch(/^W-110: not done\n\nNot done: criteria AC-2 have no passing evidence\n\nChant-Agent: factory\nChant-Lease: /);
|
|
188
|
+
const fm = parseFrontMatter(git(["show", `${kept}:work/W-110-prepared.md`], dir));
|
|
189
|
+
if (!fm.ok) throw new Error(fm.message);
|
|
190
|
+
const results = Object.fromEntries((fm.value.evidence as { criterion: string; result: string }[]).map((e) => [e.criterion, e.result]));
|
|
191
|
+
expect(results).toEqual({ "AC-1": "pass", "AC-2": "fail" });
|
|
192
|
+
|
|
193
|
+
// With both criteria passing, the item is done and its commit carries the builder's run.
|
|
194
|
+
writeFileSync(join(dir, "hooks", "check2.cjs"), `console.log(JSON.stringify({ criteria: { "AC-1": "pass", "AC-2": "pass" } }));`);
|
|
195
|
+
git(["add", "-A"], dir);
|
|
196
|
+
git(["commit", "-q", "-m", "a passing check"], dir);
|
|
197
|
+
const { readLeaseHistory } = await import("../lifecycle/work-lease");
|
|
198
|
+
const failed = (await readLeaseHistory("W-110", { cwd: dir })).records.filter((r) => r.event === "claim").at(-1) as { token: string };
|
|
199
|
+
const amended = await amendRecord({ kind: join(dir, "work", "work.kind.mjs"), id: "W-110", fields: JSON.stringify({ retry: { after: failed.token, by: "alice" } }), cwd: dir });
|
|
200
|
+
expect("error" in amended).toBe(false);
|
|
201
|
+
const done = await run(dir, "W-110", { builder: `${process.execPath} hooks/builder2.cjs`, check: `${process.execPath} hooks/check2.cjs`, after: `${process.execPath} hooks/after.cjs` });
|
|
202
|
+
expect(done.workLease).toMatchObject({ item: "W-110", outcome: "done" });
|
|
203
|
+
const body = git(["log", "-1", "--format=%B", "chant/work/W-110"], dir);
|
|
204
|
+
expect(body).toContain("Chant-Run: run-7");
|
|
205
|
+
expect(body).toContain("Chant-Agent: factory");
|
|
206
|
+
expect(body).toContain("Chant-Record: work:W-110\nChant-Record: contract:C-1");
|
|
207
|
+
expect(JSON.parse(readFileSync(join(dir, "after.json"), "utf-8")).outcome).toBe("done");
|
|
208
|
+
});
|
|
209
|
+
}, 120_000);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
describe("an ask's item is the builder's only for its plan (studio#382)", () => {
|
|
213
|
+
test("a builder that changes an open ask beyond its acceptance criteria has the item put back, and the build is not done", async () => {
|
|
214
|
+
await withTestDir(async (dir) => {
|
|
215
|
+
workspace(dir);
|
|
216
|
+
for (const id of ["W-101", "W-102"]) writeFileSync(join(dir, ".fail-" + id), "");
|
|
217
|
+
writeFileSync(join(dir, "work", "W-120-an-open-ask.md"), item("W-120", { source: { ask: { said: "Add a page.", by: "alice", via: "hud" } } }));
|
|
218
|
+
writeFileSync(
|
|
219
|
+
join(dir, "hooks", "rewrite.cjs"),
|
|
220
|
+
`const fs = require("node:fs"); const f = "work/W-120-an-open-ask.md"; fs.writeFileSync(f, fs.readFileSync(f, "utf8").replace('"title": "Item W-120"', '"title": "Renamed"')); fs.writeFileSync("app/W-120.txt", "the page\\n");`,
|
|
221
|
+
);
|
|
222
|
+
git(["add", "-A"], dir);
|
|
223
|
+
git(["commit", "-q", "-m", "an open ask"], dir);
|
|
224
|
+
const result = await run(dir, "W-120", { builder: `${process.execPath} hooks/rewrite.cjs` });
|
|
225
|
+
expect(result.workLease).toMatchObject({ item: "W-120", outcome: "not_done" });
|
|
226
|
+
const kept = git(["for-each-ref", "--format=%(refname)", "refs/chant/kept/W-120/"], dir);
|
|
227
|
+
expect(git(["show", `${kept}:work/W-120-an-open-ask.md`], dir)).toContain('"title": "Item W-120"');
|
|
228
|
+
expect(git(["log", "-1", "--format=%B", kept], dir)).toContain("the guard put back what the builder changed out of scope: work/W-120-an-open-ask.md (it changed title, and only acceptance is the builder's)");
|
|
229
|
+
});
|
|
230
|
+
}, 120_000);
|
|
231
|
+
});
|
|
232
|
+
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The factory reference Op (#3406, ws-087): the factory's rules declared in
|
|
3
|
+
* core, and an orchestrator's execution plugged in through hooks.
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* // ops/factory.op.ts
|
|
7
|
+
* export default factoryOp({
|
|
8
|
+
* builder: "node box/ops/factory/builder.mjs",
|
|
9
|
+
* });
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* One run is one build of one work item, under that item's lease, in a
|
|
13
|
+
* worktree on `chant/work/<item>`:
|
|
14
|
+
*
|
|
15
|
+
* Pick factoryPick: the buildable items, in order; the lease claims the first nobody holds
|
|
16
|
+
* Ask factoryAsk: the tier (the item's own, or the slice-tier point) and, for an ask, the
|
|
17
|
+
* understand point; an open question stops the run waiting
|
|
18
|
+
* Build factoryBuild: the `builder` hook, unless understand said not to build
|
|
19
|
+
* Check factoryCheck: the `check` hook, or the box's `factory.check`, with its evidence
|
|
20
|
+
* Record factoryRecord: done, dropped, redraft, ask or not_done, which the lease's release
|
|
21
|
+
* takes; a not_done attempt is kept at refs/chant/kept/<item>/<token> by the run
|
|
22
|
+
*
|
|
23
|
+
* The hooks are commands, run with no shell in the worktree:
|
|
24
|
+
*
|
|
25
|
+
* - `builder` (required): how a builder runs, such as Claude Code in the box
|
|
26
|
+
* or a `fountainRun` wrapper. It gets FACTORY_ITEM, FACTORY_TIER,
|
|
27
|
+
* FACTORY_TOKEN, FACTORY_HOLDER, FACTORY_WORKTREE and FACTORY_CONTEXT, edits
|
|
28
|
+
* the worktree, and exits 0 when it finished. It may print a last JSON line
|
|
29
|
+
* `{ "reverted": [paths] }` when its guard put changes back.
|
|
30
|
+
* - `context` (optional): passed to the builder as FACTORY_CONTEXT, the prompt
|
|
31
|
+
* and context bundle the orchestrator assembles.
|
|
32
|
+
* - `check` (optional): the verdict command; the box block's `factory.check`
|
|
33
|
+
* when left out. An infra profile names a plan or `chant lint` here.
|
|
34
|
+
*
|
|
35
|
+
* Publishing a done item is the box's publisher (ws-088), run later by a
|
|
36
|
+
* person or a surface with `chant workspace box publish`; it is not a step.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import { Op, phase, activity, shell, type NamedActivityStep } from "./builders";
|
|
40
|
+
import { stepOutput } from "./step-output-ref";
|
|
41
|
+
import { workLeaseOutput } from "./work-lease-run";
|
|
42
|
+
import type { OpConfig } from "./types";
|
|
43
|
+
|
|
44
|
+
export interface FactoryOpOptions {
|
|
45
|
+
/** The Op's name. `factory` by default. */
|
|
46
|
+
name?: string;
|
|
47
|
+
/** The builder hook: a command run in the run's worktree. */
|
|
48
|
+
builder: string;
|
|
49
|
+
/** The context hook: what the builder receives as FACTORY_CONTEXT. */
|
|
50
|
+
context?: string;
|
|
51
|
+
/** The check hook; the box's `factory.check` when left out. It may print `{ "criteria": { "<id>": "pass" | "fail" } }` as its last line to tick each criterion by its own result. */
|
|
52
|
+
check?: string;
|
|
53
|
+
/** The prepare hook: a command run in the workspace before Pick, such as opening the first build's item once the box's intent is decided. */
|
|
54
|
+
prepare?: string;
|
|
55
|
+
/** The after hook: a command run in the worktree once the outcome is recorded, with FACTORY_OUTCOME and FACTORY_COMMIT, such as ending the build's run record. */
|
|
56
|
+
after?: string;
|
|
57
|
+
/** The work kind file, when the declaration names several. */
|
|
58
|
+
kind?: string;
|
|
59
|
+
/** How long a lease is held without a renewal, such as 15m. The lease's default otherwise. */
|
|
60
|
+
ttl?: string;
|
|
61
|
+
/** How long the builder may run. 2h by default. */
|
|
62
|
+
buildTimeout?: string;
|
|
63
|
+
schedule?: OpConfig["schedule"];
|
|
64
|
+
/** Where the workspace is read; the run's working directory when left out. */
|
|
65
|
+
cwd?: string;
|
|
66
|
+
/** Backends for the points' model deciders, in place of `decide.backends` in chant.config. */
|
|
67
|
+
backends?: Record<string, { url: string; key?: unknown; timeoutMs?: number }>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The Op's configuration, for a steward that lists it or a test that runs it. */
|
|
71
|
+
export function factoryOpConfig(options: FactoryOpOptions): OpConfig {
|
|
72
|
+
if (!options || typeof options.builder !== "string" || options.builder.trim() === "") {
|
|
73
|
+
throw new Error("factoryOp needs a builder: the command that runs a builder in the run's worktree");
|
|
74
|
+
}
|
|
75
|
+
const lease = workLeaseOutput();
|
|
76
|
+
const kind = options.kind !== undefined ? { kind: options.kind } : {};
|
|
77
|
+
const at = options.cwd !== undefined ? { cwd: options.cwd } : {};
|
|
78
|
+
return {
|
|
79
|
+
name: options.name ?? "factory",
|
|
80
|
+
overview: "Build the next buildable work item under its lease: pick, ask, build, check and record, by the factory's rules (#3406, ws-087)",
|
|
81
|
+
workLease: {
|
|
82
|
+
item: stepOutput("pick", "candidates"),
|
|
83
|
+
outcome: stepOutput("record", "outcome"),
|
|
84
|
+
...kind,
|
|
85
|
+
...(options.ttl !== undefined ? { ttl: options.ttl } : {}),
|
|
86
|
+
},
|
|
87
|
+
changesCheckout: true,
|
|
88
|
+
...(options.schedule ? { schedule: options.schedule } : {}),
|
|
89
|
+
phases: [
|
|
90
|
+
...(options.prepare !== undefined ? [phase("Prepare", [shell(options.prepare, { ...(options.cwd !== undefined ? { cwd: options.cwd } : {}), id: "prepare" })])] : []),
|
|
91
|
+
phase("Pick", [activity("factoryPick", { ...kind, ...at }, { profile: "fastIdempotent", id: "pick" })]),
|
|
92
|
+
phase("Ask", [activity("factoryAsk", { lease, ...kind, ...at, ...(options.backends ? { backends: options.backends } : {}) }, { profile: "fastIdempotent", id: "ask" })]),
|
|
93
|
+
phase("Build", [
|
|
94
|
+
activity(
|
|
95
|
+
"factoryBuild",
|
|
96
|
+
{ lease, ...at, ask: stepOutput("ask"), builder: options.builder, ...(options.context !== undefined ? { context: options.context } : {}) },
|
|
97
|
+
{ profile: "atMostOnce", id: "build", timeout: options.buildTimeout ?? "2h" },
|
|
98
|
+
),
|
|
99
|
+
]),
|
|
100
|
+
phase("Check", [activity("factoryCheck", { lease, ...at, build: stepOutput("build"), ...(options.check !== undefined ? { check: options.check } : {}) }, { profile: "atMostOnce", id: "check" })]),
|
|
101
|
+
phase("Record", [activity("factoryRecord", { lease, ...at, ask: stepOutput("ask"), build: stepOutput("build"), check: stepOutput("check"), ...(options.after !== undefined ? { after: options.after } : {}) }, { profile: "atMostOnce", id: "record" })]),
|
|
102
|
+
],
|
|
103
|
+
} as OpConfig;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** The factory reference Op, as an `*.op.ts` file exports it. */
|
|
107
|
+
export function factoryOp(options: FactoryOpOptions): ReturnType<typeof Op> {
|
|
108
|
+
return Op(factoryOpConfig(options));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* The steward's ready step for the factory Op (#382): the keys of the work buildable now, for
|
|
113
|
+
* `declareSteward({ beside: [{ op: factory, ready: factoryReady({ cwd }) }] })`. `also` names a
|
|
114
|
+
* command printing a JSON list of more keys, for work the pick can't see yet.
|
|
115
|
+
*/
|
|
116
|
+
export function factoryReady(options: { cwd?: string; kind?: string; also?: string } = {}): NamedActivityStep {
|
|
117
|
+
return activity("factoryReady", { ...options }, { profile: "fastIdempotent" });
|
|
118
|
+
}
|
|
@@ -191,6 +191,80 @@ describe("gate approval — policy decisions (#2508)", () => {
|
|
|
191
191
|
expect(check.satisfied).toBe(false);
|
|
192
192
|
});
|
|
193
193
|
|
|
194
|
+
test("log-only then enforce: a permit recorded under log-only does not pass the enforced gate (#2512)", async () => {
|
|
195
|
+
const logOnly: ResolvedGateApproval = { policy: POLICY, mode: "log-only", context: { risk: "low" } };
|
|
196
|
+
const enforce: ResolvedGateApproval = { ...logOnly, mode: "enforce" };
|
|
197
|
+
// The pending fact was recorded while the gate was log-only, and the agent's allow was recorded against it.
|
|
198
|
+
const port = memoryGateLedgerPort({
|
|
199
|
+
resolutions: [approval({ resolvedBy: "release-bot", timestamp: "2026-09-01T01:00:00.000Z", approver: { kind: "agent" }, policyDecision: allow("log-only") })],
|
|
200
|
+
pending: [{ ...PENDING, approval: logOnly }],
|
|
201
|
+
});
|
|
202
|
+
const check = await evaluateGate(port, { op: "release", gate: "ship", planDigest: PLAN_A, approval: enforce, now: NOW });
|
|
203
|
+
expect(check.satisfied).toBe(false);
|
|
204
|
+
|
|
205
|
+
const tally = tallyGateApprovals(
|
|
206
|
+
[approval({ resolvedBy: "release-bot", timestamp: "2026-09-01T01:00:00.000Z", approver: { kind: "agent" }, policyDecision: allow("log-only") })],
|
|
207
|
+
"ship", PENDING.timestamp, PLAN_A, enforce,
|
|
208
|
+
);
|
|
209
|
+
expect(tally.permit).toBeUndefined();
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test("log-only then enforce: a human's approval recorded under log-only still counts toward the quorum (#2512)", async () => {
|
|
213
|
+
const logOnly: ResolvedGateApproval = { quorum: { count: 1 }, policy: POLICY, mode: "log-only" };
|
|
214
|
+
const enforce: ResolvedGateApproval = { ...logOnly, mode: "enforce" };
|
|
215
|
+
const port = memoryGateLedgerPort({
|
|
216
|
+
resolutions: [approval({ resolvedBy: "alex", timestamp: "2026-09-01T01:00:00.000Z", policyDecision: allow("log-only") })],
|
|
217
|
+
pending: [{ ...PENDING, approval: logOnly }],
|
|
218
|
+
});
|
|
219
|
+
const check = await evaluateGate(port, { op: "release", gate: "ship", planDigest: PLAN_A, approval: enforce, now: NOW });
|
|
220
|
+
expect(check.satisfied).toBe(true);
|
|
221
|
+
if (!check.satisfied) return;
|
|
222
|
+
expect(check.via).toBe("quorum");
|
|
223
|
+
expect(check.resolution.resolvedBy).toBe("alex");
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test("log-only then enforce: the gated run re-records the pending fact under enforce, and only a fresh permit passes (#2512)", async () => {
|
|
227
|
+
const logOnly: ResolvedGateApproval = { policy: POLICY, mode: "log-only", context: { risk: "low" } };
|
|
228
|
+
const enforce: ResolvedGateApproval = { ...logOnly, mode: "enforce" };
|
|
229
|
+
const stale = approval({ resolvedBy: "release-bot", timestamp: "2026-09-01T01:00:00.000Z", approver: { kind: "agent" }, policyDecision: allow("log-only") });
|
|
230
|
+
const port = memoryGateLedgerPort({ resolutions: [stale], pending: [{ ...PENDING, approval: logOnly }] });
|
|
231
|
+
|
|
232
|
+
const first = await evaluateGate(port, { op: "release", gate: "ship", planDigest: PLAN_A, approval: enforce, now: NOW });
|
|
233
|
+
expect(first.satisfied).toBe(false);
|
|
234
|
+
if (first.satisfied) return;
|
|
235
|
+
// The standing fact described the log-only gate, so a fresh one carries
|
|
236
|
+
// enforce, and `chant approve` evaluates the next agent approval under it.
|
|
237
|
+
expect(first.recorded).toBe(true);
|
|
238
|
+
expect(first.pending.approval?.mode).toBe("enforce");
|
|
239
|
+
|
|
240
|
+
// Re-running without a new approval still waits: the stale permit predates the new pending fact as well.
|
|
241
|
+
const again = await evaluateGate(port, { op: "release", gate: "ship", planDigest: PLAN_A, approval: enforce, now: "2026-09-01T12:30:00.000Z" });
|
|
242
|
+
expect(again.satisfied).toBe(false);
|
|
243
|
+
|
|
244
|
+
const fresh = approval({ resolvedBy: "release-bot", timestamp: "2026-09-01T13:00:00.000Z", approver: { kind: "agent" }, policyDecision: allow("enforce") });
|
|
245
|
+
const after = await evaluateGate(
|
|
246
|
+
memoryGateLedgerPort({ resolutions: [stale, fresh], pending: [first.pending] }),
|
|
247
|
+
{ op: "release", gate: "ship", planDigest: PLAN_A, approval: enforce, now: "2026-09-01T14:00:00.000Z" },
|
|
248
|
+
);
|
|
249
|
+
expect(after.satisfied).toBe(true);
|
|
250
|
+
if (!after.satisfied) return;
|
|
251
|
+
expect(after.via).toBe("policy");
|
|
252
|
+
expect(after.resolution.timestamp).toBe(fresh.timestamp);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test("enforce: a recorded decision with no mode does not pass the gate (#2512)", async () => {
|
|
256
|
+
const block: ResolvedGateApproval = { policy: POLICY, mode: "enforce" };
|
|
257
|
+
const { mode: _mode, ...noMode } = allow()!;
|
|
258
|
+
const { check } = await decide(
|
|
259
|
+
[approval({
|
|
260
|
+
resolvedBy: "release-bot", timestamp: "2026-09-01T01:00:00.000Z", approver: { kind: "agent" },
|
|
261
|
+
policyDecision: noMode as GateResolutionRecord["policyDecision"],
|
|
262
|
+
})],
|
|
263
|
+
block,
|
|
264
|
+
);
|
|
265
|
+
expect(check.satisfied).toBe(false);
|
|
266
|
+
});
|
|
267
|
+
|
|
194
268
|
test("enforce: a deny-by-default policy keeps the old behaviour, where only a human's approval counts", async () => {
|
|
195
269
|
const block: ResolvedGateApproval = { policy: POLICY, mode: "enforce" };
|
|
196
270
|
const deny = { ...allow()!, decision: "deny" as const, determining: ["floor"] };
|
package/src/op/gate-approval.ts
CHANGED
|
@@ -119,7 +119,11 @@ export interface GatePolicyAnswer {
|
|
|
119
119
|
export interface GatePolicyDecision extends GatePolicyAnswer {
|
|
120
120
|
policy: string;
|
|
121
121
|
version: string;
|
|
122
|
-
/**
|
|
122
|
+
/**
|
|
123
|
+
* The gate's mode when the decision was recorded. A permit passes a gate
|
|
124
|
+
* only when both this and the gate's current mode are `enforce` (#2512), so
|
|
125
|
+
* an allow recorded under `log-only` never binds after a switch.
|
|
126
|
+
*/
|
|
123
127
|
mode: GateApprovalMode;
|
|
124
128
|
}
|
|
125
129
|
|
package/src/op/gate.ts
CHANGED
|
@@ -74,6 +74,19 @@ export interface PendingGatePush {
|
|
|
74
74
|
export interface GateLedgerPort {
|
|
75
75
|
read(op: string): Promise<{ resolutions: GateResolutionRecord[]; pending: PendingGateRecord[] }>;
|
|
76
76
|
appendPending(input: PendingGateInput): Promise<PendingGatePush>;
|
|
77
|
+
/**
|
|
78
|
+
* The rule a workspace sets for approvals of `gate` (#3163, ws-080), or
|
|
79
|
+
* null when none applies: the declaration's `identity.gates`, read at base.
|
|
80
|
+
* Absent, every approval is judged as before.
|
|
81
|
+
*/
|
|
82
|
+
approvalRule?(gate: string): Promise<GateApprovalRule | null>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** A workspace's rule for which approvals of a gate count (#3163): a signed approval, from a class when it names one. */
|
|
86
|
+
export interface GateApprovalRule {
|
|
87
|
+
requirement: { gate: string; class: string | null };
|
|
88
|
+
/** Why `approval` doesn't count, or null when it does. */
|
|
89
|
+
refuses(approval: GateResolutionRecord): string | null;
|
|
77
90
|
}
|
|
78
91
|
|
|
79
92
|
/**
|
|
@@ -102,6 +115,11 @@ export function gitGateLedgerPort(opts?: { cwd?: string }): GateLedgerPort {
|
|
|
102
115
|
const { resolutions, pending } = await readGateLedger(op, opts);
|
|
103
116
|
return { resolutions, pending };
|
|
104
117
|
},
|
|
118
|
+
async approvalRule(gate) {
|
|
119
|
+
// Loaded on demand: the rule lives in the workspace modules, which import the Op modules.
|
|
120
|
+
const { gateAdmission } = await import("../workspace/identity");
|
|
121
|
+
return gateAdmission(opts?.cwd ?? process.cwd(), gate);
|
|
122
|
+
},
|
|
105
123
|
async appendPending(input) {
|
|
106
124
|
const { record } = await appendPendingGate(input, opts);
|
|
107
125
|
try {
|
|
@@ -288,7 +306,7 @@ export interface GateTally {
|
|
|
288
306
|
counted: GateResolutionRecord[];
|
|
289
307
|
/** The quorum's count, 1 when the gate declares none. */
|
|
290
308
|
need: number;
|
|
291
|
-
/** The newest approval whose recorded permit passes the gate on its own. Only in `enforce` mode, and only under the gate's current policy version. */
|
|
309
|
+
/** The newest approval whose recorded permit passes the gate on its own. Only in `enforce` mode, only for a permit recorded under `enforce`, and only under the gate's current policy version. */
|
|
292
310
|
permit?: GateResolutionRecord;
|
|
293
311
|
/** The newest approval of this gate for a different plan, when the gate binds one. */
|
|
294
312
|
mismatched?: GateResolutionRecord;
|
|
@@ -305,8 +323,9 @@ export interface GateTally {
|
|
|
305
323
|
* Of those, only a human's counts toward the quorum, once per `resolvedBy`,
|
|
306
324
|
* and only with one of the quorum's roles when it names any. An agent counts
|
|
307
325
|
* only through a recorded `allow` in `enforce` mode, evaluated under the
|
|
308
|
-
* policy version the gate declares now
|
|
309
|
-
*
|
|
326
|
+
* policy version the gate declares now and recorded while the gate was in
|
|
327
|
+
* `enforce` (#2512). A `log-only` decision never changes the outcome, even
|
|
328
|
+
* after the gate switches to `enforce`, and a `deny` never removes a human's approval: a policy can add
|
|
310
329
|
* a way through the gate but cannot take one away.
|
|
311
330
|
*/
|
|
312
331
|
export function tallyGateApprovals(
|
|
@@ -331,8 +350,12 @@ export function tallyGateApprovals(
|
|
|
331
350
|
}
|
|
332
351
|
|
|
333
352
|
const decision = r.policyDecision;
|
|
353
|
+
// #2512: the decision must also have been recorded under `enforce`. An
|
|
354
|
+
// allow recorded while the gate was log-only was never binding, and a
|
|
355
|
+
// later switch to enforce does not make it binding.
|
|
334
356
|
if (
|
|
335
357
|
approval.mode === "enforce" && approval.policy && decision?.decision === "allow" &&
|
|
358
|
+
decision.mode === "enforce" &&
|
|
336
359
|
decision.version === approval.policy.version && (!permit || at(r) >= at(permit))
|
|
337
360
|
) {
|
|
338
361
|
permit = r;
|
|
@@ -394,10 +417,13 @@ export async function evaluateGate(port: GateLedgerPort, input: GateCheckInput):
|
|
|
394
417
|
// a mismatch with a message instead of silently not counting.
|
|
395
418
|
const env = input.environment;
|
|
396
419
|
const pending = env === undefined ? ledger.pending : ledger.pending.filter((p) => p.environment === env);
|
|
397
|
-
|
|
420
|
+
// #3163: a gate the workspace's identity.gates names counts only signed approvals from its class.
|
|
421
|
+
const rule = (await port.approvalRule?.(input.gate)) ?? null;
|
|
422
|
+
const resolutions = (
|
|
398
423
|
env === undefined
|
|
399
424
|
? ledger.resolutions
|
|
400
|
-
: ledger.resolutions.filter((r) => r.environment === undefined || r.environment === env)
|
|
425
|
+
: ledger.resolutions.filter((r) => r.environment === undefined || r.environment === env)
|
|
426
|
+
).filter((r) => rule === null || r.gate !== input.gate || rule.refuses(r) === null);
|
|
401
427
|
|
|
402
428
|
const standing = latestPendingGate(pending, input.gate);
|
|
403
429
|
const since = standing?.timestamp ?? EPOCH;
|
package/src/op/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { Op, phase, activity, gate, effect, build, kubectlApply, helmInstall, helmInstallPinned, waitForStack, waitForReady,
|
|
2
|
-
gitlabPipeline, lifecycleSnapshot, shell, sourceArchive, releasePlan, releaseRecord, releaseRollbackPlan, releaseRollbackRecord, decide, ensureSecret, teardown, envTeardown, k3dUp, k3dDown,
|
|
2
|
+
gitlabPipeline, lifecycleSnapshot, shell, sourceArchive, releasePlan, releaseRecord, composeChangeSet, lifecyclePlanChangeSet, readChangeSetPart, releaseRollbackPlan, releaseRollbackRecord, decide, ensureSecret, teardown, envTeardown, k3dUp, k3dDown,
|
|
3
3
|
k3sInstall, k3sUninstall, flociUp, flociDown,
|
|
4
4
|
flociAzUp, flociAzDown, flociGcpUp, flociGcpDown, httpCheck,
|
|
5
5
|
azGroupEnsure, azGroupDelete, azApply, azDelete, awsApply, awsDelete, gcpApply, gcpDelete, policyGate,
|
|
@@ -60,6 +60,8 @@ export type { ActivityProfile, ActivityProfileName } from "./activity-profiles";
|
|
|
60
60
|
export { NonRetryableActivityError, nonRetryableFailure } from "./activity-failure";
|
|
61
61
|
export { runOpLocally, parseDuration, OpRunFailure } from "./local-executor";
|
|
62
62
|
export type { StepRecord, OpRunResult, RunOpOptions } from "./local-executor";
|
|
63
|
+
export { currentOpRun, withOpRunContext } from "./run-context";
|
|
64
|
+
export type { OpRunContext, PassedGate } from "./run-context";
|
|
63
65
|
export { evaluateGate, gitGateLedgerPort, memoryGateLedgerPort, approveCommand, describeGateMismatch } from "./gate";
|
|
64
66
|
export type { GateLedgerPort, GateCheck, GateCheckInput, PendingGatePush, GateDigestMismatch, GateQuorumProgress, GateTally } from "./gate";
|
|
65
67
|
export { tallyGateApprovals, approverOf } from "./gate";
|
|
@@ -147,3 +149,11 @@ export type { ChangeSignalGate, ChangeSignalGateOptions, WakeReason } from "./ch
|
|
|
147
149
|
export { classifyOpVerbClass, isGated } from "./op-verb-class";
|
|
148
150
|
export type { OpVerbClass } from "./op-verb-class";
|
|
149
151
|
export { takeProfileAndId } from "./builders";
|
|
152
|
+
|
|
153
|
+
// The factory reference Op (#3406, ws-087).
|
|
154
|
+
export { factoryOp, factoryOpConfig, factoryReady } from "./factory";
|
|
155
|
+
// The factory's rules themselves (#3406) are values in workspace code, which no level 0 command may load (#2526), so only their
|
|
156
|
+
// types are re-exported here. An orchestrator's own tools, such as a retry command a UI runs, import the values from
|
|
157
|
+
// `@intentius/chant/workspace/factory-rules`.
|
|
158
|
+
export type { FactoryItem, FactoryContext, FactoryClaim, FactoryHold, RetryState } from "../workspace/factory-rules";
|
|
159
|
+
export type { FactoryOpOptions } from "./factory";
|
|
@@ -232,6 +232,21 @@ describe("runOpLocally — outcomeAttribute", () => {
|
|
|
232
232
|
expect(result.records[0].outcome).toEqual({ name: "Drift", value: true });
|
|
233
233
|
});
|
|
234
234
|
|
|
235
|
+
test("an attribute whose path the result does not hold is left off, not recorded as undefined", async () => {
|
|
236
|
+
const done: ActivityFn = async () => ({ state: "done", switched: false });
|
|
237
|
+
const config = op({
|
|
238
|
+
phases: [{ name: "Switch", steps: [
|
|
239
|
+
{ kind: "activity", fn: "migrationSwitch", outcomeAttribute: [{ name: "OldColumn", from: "oldColumn" }, { name: "State", from: "state" }] },
|
|
240
|
+
{ kind: "activity", fn: "migrationRetain", outcomeAttribute: { name: "RetainUntil", from: "retainUntil" } },
|
|
241
|
+
] }],
|
|
242
|
+
});
|
|
243
|
+
const result = await runOpLocally(config, new Map([["migrationSwitch", done], ["migrationRetain", done]]), PROFILES);
|
|
244
|
+
expect(result.records[0].outcomes).toEqual([{ name: "State", value: "done" }]);
|
|
245
|
+
expect(result.records[0].outcome).toEqual({ name: "State", value: "done" });
|
|
246
|
+
expect(result.records[1].outcomes).toBeUndefined();
|
|
247
|
+
expect(result.records[1].outcome).toBeUndefined();
|
|
248
|
+
});
|
|
249
|
+
|
|
235
250
|
test("a step with no attribute leaves both fields absent", async () => {
|
|
236
251
|
const noop: ActivityFn = async () => ({ ok: true });
|
|
237
252
|
const config = op({ phases: [{ name: "Plan", steps: [{ kind: "activity", fn: "noop" }] }] });
|