@intentius/chant 0.102.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/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/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/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/fan-out.d.ts +33 -0
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/handlers/operator.d.ts +7 -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/main.d.ts.map +1 -1
- package/dist/cli/mcp/op-tools.d.ts.map +1 -1
- package/dist/cli/mcp/workspace-tools.d.ts +3 -2
- package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +63 -1
- package/dist/cli/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.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/verbs/shell.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/gated-waves.d.ts +147 -0
- package/dist/gated-waves.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/lexicon.d.ts +48 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.d.ts +7 -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/lint/post-synth.d.ts +18 -0
- package/dist/lint/post-synth.d.ts.map +1 -1
- package/dist/op/activities/activity-contracts.d.ts +126 -0
- package/dist/op/activities/activity-contracts.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 +4 -0
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activity-contract.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/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/factory.d.ts +83 -0
- package/dist/op/factory.d.ts.map +1 -0
- package/dist/op/index.d.ts +4 -1
- package/dist/op/index.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/types.d.ts +8 -0
- package/dist/op/types.d.ts.map +1 -1
- 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/telemetry-attribution.d.ts +31 -0
- package/dist/telemetry-attribution.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/workspace/box-cli.d.ts +12 -0
- package/dist/workspace/box-cli.d.ts.map +1 -0
- package/dist/workspace/box-factory.d.ts +27 -3
- package/dist/workspace/box-factory.d.ts.map +1 -1
- 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/checks.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 +2 -1
- package/dist/workspace/conformance/index.d.ts.map +1 -1
- package/dist/workspace/conformance/vitest.d.ts +9 -2
- package/dist/workspace/conformance/vitest.d.ts.map +1 -1
- package/dist/workspace/conformance/writer.d.ts +108 -14
- package/dist/workspace/conformance/writer.d.ts.map +1 -1
- 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 +65 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.schema.json +153 -4
- 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/init.d.ts +21 -0
- package/dist/workspace/init.d.ts.map +1 -1
- package/dist/workspace/intent-cli.d.ts +1 -1
- package/dist/workspace/intent-cli.d.ts.map +1 -1
- package/dist/workspace/intent-record.d.ts +28 -1
- package/dist/workspace/intent-record.d.ts.map +1 -1
- package/dist/workspace/intent.d.ts +102 -5
- 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 +48 -0
- 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-upgrade.d.ts +31 -2
- package/dist/workspace/lineage-upgrade.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/reason-codes.d.ts +33 -4
- package/dist/workspace/reason-codes.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-cli.d.ts +7 -0
- 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-write.d.ts +66 -6
- package/dist/workspace/records-write.d.ts.map +1 -1
- package/dist/workspace/records.d.ts +28 -9
- package/dist/workspace/records.d.ts.map +1 -1
- package/dist/workspace/runs-cli.d.ts +138 -6
- package/dist/workspace/runs-cli.d.ts.map +1 -1
- package/dist/workspace/runs.d.ts +132 -11
- package/dist/workspace/runs.d.ts.map +1 -1
- package/dist/workspace/session-kinds.d.ts +1 -1
- package/dist/workspace/session-kinds.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 +2 -0
- package/dist/workspace/status-gates.d.ts.map +1 -1
- package/dist/workspace/status.d.ts +54 -0
- package/dist/workspace/status.d.ts.map +1 -1
- package/dist/workspace/symbols.d.ts +16 -5
- package/dist/workspace/symbols.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 +6 -1
- package/dist/workspace/trust/seal.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.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/workspace-kinds.schema.json +122 -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 +6 -2
- package/dist/workspace/write-scope.d.ts.map +1 -1
- package/package.json +6 -1
- 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/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/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/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/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/operator.test.ts +21 -0
- package/src/cli/handlers/operator.ts +34 -3
- package/src/cli/handlers/pr.ts +373 -0
- package/src/cli/handlers/run.ts +1 -0
- package/src/cli/main.test.ts +20 -0
- package/src/cli/main.ts +185 -23
- 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 +1 -1
- package/src/cli/mcp/workspace-tools.ts +34 -6
- package/src/cli/registry.ts +63 -1
- 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/discovery/sandbox/bundle.ts +1 -1
- 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/index.ts +1 -0
- package/src/lexicon.ts +51 -0
- package/src/lifecycle/gate-ledger.ts +7 -0
- package/src/lifecycle/git.ts +102 -16
- 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/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 +6 -0
- 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/decide-backend.ts +5 -1
- package/src/op/decide-read-inputs.ts +33 -5
- package/src/op/factory.e2e.test.ts +232 -0
- package/src/op/factory.ts +118 -0
- package/src/op/index.ts +9 -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/types.ts +8 -0
- package/src/op/work-lease-run.ts +3 -0
- 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/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/__fixtures__/studio-template-84c8b21.json +1 -1
- package/src/workspace/app-kind.test.ts +196 -0
- package/src/workspace/box-cli.ts +113 -0
- package/src/workspace/box-factory.test.ts +4 -1
- package/src/workspace/box-factory.ts +37 -4
- 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/chant-migrations.e2e.test.ts +17 -5
- package/src/workspace/checks/boxes.test.ts +3 -0
- package/src/workspace/checks.ts +10 -3
- package/src/workspace/component-environments.test.ts +105 -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 +7 -2
- package/src/workspace/conformance/__writer_fixture__/answers/answer.schema.json +42 -2
- 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 +96 -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 +14 -8
- package/src/workspace/conformance/__writer_fixture__/sessions/session.schema.json +187 -6
- package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +25 -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 +2 -1
- package/src/workspace/conformance/index.mjs +22 -1
- package/src/workspace/conformance/index.ts +5 -1
- package/src/workspace/conformance/vitest.ts +33 -1
- package/src/workspace/conformance/writer-conformance.e2e.test.ts +49 -3
- package/src/workspace/conformance/writer.test.ts +32 -2
- package/src/workspace/conformance/writer.ts +339 -28
- package/src/workspace/decide.test.ts +208 -4
- package/src/workspace/decide.ts +427 -73
- package/src/workspace/decision-points.schema.json +11 -4
- package/src/workspace/declaration.schema.json +153 -4
- package/src/workspace/declaration.ts +141 -0
- 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 +2 -2
- package/src/workspace/graph.schema.json +67 -0
- package/src/workspace/identity.test.ts +23 -0
- package/src/workspace/init.ts +82 -2
- package/src/workspace/intent-cli.ts +26 -9
- package/src/workspace/intent-patch-id.test.ts +162 -0
- package/src/workspace/intent-record.schema.json +130 -3
- package/src/workspace/intent-record.ts +74 -28
- 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 +5 -5
- package/src/workspace/intent.schema.json +234 -4
- package/src/workspace/intent.ts +260 -66
- 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 +157 -2
- 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 +162 -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/profiles.test.ts +159 -0
- package/src/workspace/read-contract.test.ts +2 -2
- package/src/workspace/reason-codes.test.ts +13 -1
- package/src/workspace/reason-codes.ts +38 -4
- package/src/workspace/record-sessions.ts +9 -8
- package/src/workspace/records-amend.schema.json +78 -0
- package/src/workspace/records-cli.ts +20 -6
- package/src/workspace/records-close.schema.json +78 -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 +7 -0
- package/src/workspace/records-review.schema.json +78 -0
- package/src/workspace/records-sessions-write.test.ts +69 -0
- package/src/workspace/records-sessions.test.ts +22 -0
- package/src/workspace/records-write.ts +192 -25
- package/src/workspace/records.schema.json +16 -0
- package/src/workspace/records.ts +50 -9
- 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 +343 -14
- package/src/workspace/runs-statement.test.ts +239 -0
- package/src/workspace/runs-write.schema.json +416 -5
- package/src/workspace/runs.schema.json +451 -4
- package/src/workspace/runs.test.ts +39 -0
- package/src/workspace/runs.ts +296 -35
- package/src/workspace/session-kinds.ts +3 -3
- package/src/workspace/squash.ts +198 -0
- package/src/workspace/status-contract.test.ts +5 -2
- package/src/workspace/status-gates.ts +3 -1
- package/src/workspace/status.schema.json +126 -3
- package/src/workspace/status.ts +90 -1
- package/src/workspace/symbols.ts +20 -9
- package/src/workspace/trust/run-commits.ts +115 -0
- package/src/workspace/trust/run-statement.ts +226 -0
- package/src/workspace/trust/seal.ts +8 -2
- 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.ts +3 -0
- package/src/workspace/work-evidence.schema.json +3 -0
- package/src/workspace/work-size.test.ts +77 -0
- package/src/workspace/work-size.ts +93 -0
- package/src/workspace/workspace-kinds.schema.json +122 -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 +16 -1
- package/src/workspace/write-scope.ts +33 -11
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The broker protocol for box capabilities, version 1 (#3164, ws-097).
|
|
3
|
+
*
|
|
4
|
+
* A box holds no credential (#2726). Each capability its box block declares,
|
|
5
|
+
* `{ name, broker, scope }`, is reached through a broker: a runtime that
|
|
6
|
+
* holds the credential, swaps the box's own token for it, and refuses what
|
|
7
|
+
* the box's declaration does not allow. chant never runs a broker (ws-052,
|
|
8
|
+
* ws-086). It specifies one here, so studio's lobby, fountain's broker or a
|
|
9
|
+
* self-hosted one serve the same box without the box changing its
|
|
10
|
+
* declaration, and `runBrokerConformance` in
|
|
11
|
+
* `@intentius/chant/workspace/conformance` checks an implementation.
|
|
12
|
+
*
|
|
13
|
+
* Every route is relative to the broker's base URL, and every request
|
|
14
|
+
* carries the box's own token: `Authorization: Bearer <token>`, or for
|
|
15
|
+
* inference `x-api-key: <token>` as the Anthropic SDK sends it, or for egress
|
|
16
|
+
* wherever the API expects its key. How a box gets its token (studio's
|
|
17
|
+
* door claiming it, fountain's sandbox callback token) is the broker's own.
|
|
18
|
+
*
|
|
19
|
+
* | Route | Capability, scope word | Does |
|
|
20
|
+
* |---|---|---|
|
|
21
|
+
* | `POST /api/box/declaration` | none | the box's steward reports its block's capabilities (`declarationReport`) |
|
|
22
|
+
* | `/llm/anthropic/...` | `inference`, `agent` | the Anthropic API, with the payer's credential in place of the box's token |
|
|
23
|
+
* | `POST /decide/v1/systemone` | `inference`, `decide` | chant's decide wire format (op/decide-backend.ts), answers with `reason` (#3345) |
|
|
24
|
+
* | `/egress/<NAME>/...` | `egress`, `<NAME>` | the secret's one host, with the secret's value in place of the box's token |
|
|
25
|
+
* | `POST /api/feedback` | `feedback`, `agent` for `entries` and `passive` for `counts` | the box's agents' feedback (studio#257) |
|
|
26
|
+
* | `/fountain/api/...` | `fountain`, `agent`, `conversations`, `sandboxes`, `vault` | Fountain's API, with the operator's Fountain key |
|
|
27
|
+
*
|
|
28
|
+
* A broker may say whose credential pays (#3474, ws-098): `payer` on the
|
|
29
|
+
* declaration's answer and the `chant-payer` header on each inference and
|
|
30
|
+
* decide answer ({@link BrokerPayer}). It is optional within version 1.
|
|
31
|
+
* A box names whose turn a request is with a `chant-grant` header the broker
|
|
32
|
+
* issued (#3477, ws-099); the broker then may spend that person's credential.
|
|
33
|
+
*
|
|
34
|
+
* The rule every broker keeps: a request that needs scope word `w` of
|
|
35
|
+
* capability `c` is refused with a 403 unless the box's last report holds an
|
|
36
|
+
* entry named `c`, brokered by this broker, whose scope lists `w`. A box that
|
|
37
|
+
* has never reported is refused. The refusal's message names `c` and `w`, so
|
|
38
|
+
* whoever reads it knows which entry to add. The box's token never reaches
|
|
39
|
+
* an upstream, and the credential or secret never comes back to the box.
|
|
40
|
+
*
|
|
41
|
+
* The `broker` word in a box block names the broker the box reaches, not an
|
|
42
|
+
* implementation: an implementation is configured with the word it answers
|
|
43
|
+
* for (`lobby` for studio's), so a box moves between implementations by
|
|
44
|
+
* where its broker URL points.
|
|
45
|
+
*
|
|
46
|
+
* A capability a lexicon or a runtime adds is a {@link BrokerCapabilitySpec}:
|
|
47
|
+
* its own route prefix, and a function from a request to the scope words it
|
|
48
|
+
* needs. `runBrokerConformance` takes extra specs and holds them to the same
|
|
49
|
+
* rule.
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
/** The protocol's version, carried by the schema's `$id`. */
|
|
53
|
+
export const BROKER_PROTOCOL_VERSION = 1;
|
|
54
|
+
|
|
55
|
+
/** The bodies' schema, beside this module. */
|
|
56
|
+
export const BROKER_PROTOCOL_SCHEMA = "broker-protocol.schema.json";
|
|
57
|
+
|
|
58
|
+
/** The routes, relative to the broker's base URL. */
|
|
59
|
+
export const BROKER_ROUTES = {
|
|
60
|
+
declaration: "/api/box/declaration",
|
|
61
|
+
inference: "/llm/anthropic",
|
|
62
|
+
decide: "/decide/v1/systemone",
|
|
63
|
+
egress: "/egress",
|
|
64
|
+
feedback: "/api/feedback",
|
|
65
|
+
fountain: "/fountain",
|
|
66
|
+
} as const;
|
|
67
|
+
|
|
68
|
+
/** What the Anthropic API proxy forwards, under {@link BROKER_ROUTES.inference}. Anything else is a 404. */
|
|
69
|
+
export const INFERENCE_FORWARDED = [
|
|
70
|
+
{ method: "POST", path: /^\/v1\/messages(\/count_tokens)?$/ },
|
|
71
|
+
{ method: "GET", path: /^\/v1\/models(\/[\w.-]+)?$/ },
|
|
72
|
+
] as const;
|
|
73
|
+
|
|
74
|
+
/** A secret's name in an egress path, and so a word of the `egress` scope. */
|
|
75
|
+
export const EGRESS_SECRET_NAME = /^[A-Z][A-Z0-9_]{0,39}$/;
|
|
76
|
+
|
|
77
|
+
/** The limits on a declaration report. */
|
|
78
|
+
export const DECLARATION_LIMITS = { bytes: 32 * 1024, capabilities: 20, scopeWords: 50, wordLength: 64 } as const;
|
|
79
|
+
|
|
80
|
+
/** A capability's name, as the declaration's kind-name grammar and the lobby both allow it. */
|
|
81
|
+
export const CAPABILITY_NAME = /^[a-z][a-z0-9-]{0,62}$/;
|
|
82
|
+
|
|
83
|
+
/** A request as a broker sees it, for {@link BrokerCapabilitySpec.scopes}. */
|
|
84
|
+
export interface BrokerRequest {
|
|
85
|
+
method: string;
|
|
86
|
+
/** The path from the broker's base URL, without the query. */
|
|
87
|
+
path: string;
|
|
88
|
+
/** The parsed JSON body, when the route reads one. */
|
|
89
|
+
body?: unknown;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* One capability's broker side: the route prefixes it owns, the scope words
|
|
94
|
+
* it knows, and the words a request needs.
|
|
95
|
+
*/
|
|
96
|
+
export interface BrokerCapabilitySpec {
|
|
97
|
+
name: string;
|
|
98
|
+
/** Path prefixes, from the broker's base URL, this capability owns. */
|
|
99
|
+
routes: readonly string[];
|
|
100
|
+
/** The scope words and what each lets the box reach. `"<NAME>"` stands for an open set, such as egress's secret names. */
|
|
101
|
+
scopeWords: Readonly<Record<string, string>>;
|
|
102
|
+
/**
|
|
103
|
+
* The scope words `request` needs, all of them; `[]` when it needs none
|
|
104
|
+
* (inference's `/api/hello`); null when the request is not this
|
|
105
|
+
* capability's, or names nothing the capability serves (a 404).
|
|
106
|
+
*/
|
|
107
|
+
scopes(request: BrokerRequest): string[] | null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const under = (path: string, prefix: string): string | null => (path === prefix ? "/" : path.startsWith(`${prefix}/`) ? path.slice(prefix.length) : null);
|
|
111
|
+
|
|
112
|
+
/** `inference`: the model proxy (`agent`) and the decide endpoint (`decide`). */
|
|
113
|
+
export const INFERENCE_CAPABILITY: BrokerCapabilitySpec = {
|
|
114
|
+
name: "inference",
|
|
115
|
+
routes: [BROKER_ROUTES.inference, "/decide"],
|
|
116
|
+
scopeWords: {
|
|
117
|
+
agent: "the box's agent calls the model through /llm/anthropic",
|
|
118
|
+
decide: "the box's decision points are answered at /decide/v1/systemone",
|
|
119
|
+
},
|
|
120
|
+
scopes({ method, path }) {
|
|
121
|
+
const rest = under(path, BROKER_ROUTES.inference);
|
|
122
|
+
if (rest !== null) {
|
|
123
|
+
if (rest === "/api/hello" && (method === "HEAD" || method === "GET")) return [];
|
|
124
|
+
return INFERENCE_FORWARDED.some((r) => r.method === method && r.path.test(rest)) ? ["agent"] : null;
|
|
125
|
+
}
|
|
126
|
+
if (path === BROKER_ROUTES.decide && method === "POST") return ["decide"];
|
|
127
|
+
return null;
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/** `egress`: a secret the broker holds, used against its one host, by name. */
|
|
132
|
+
export const EGRESS_CAPABILITY: BrokerCapabilitySpec = {
|
|
133
|
+
name: "egress",
|
|
134
|
+
routes: [BROKER_ROUTES.egress],
|
|
135
|
+
scopeWords: { "<NAME>": "the secret NAME, sent only to the host its owner chose, at /egress/NAME/..." },
|
|
136
|
+
scopes({ path }) {
|
|
137
|
+
const rest = under(path, BROKER_ROUTES.egress);
|
|
138
|
+
if (rest === null) return null;
|
|
139
|
+
const name = rest.split("/")[1] ?? "";
|
|
140
|
+
return EGRESS_SECRET_NAME.test(name) ? [name] : null;
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
/** The scope words a feedback batch needs: `agent` for entries, `passive` for counts. */
|
|
145
|
+
export function feedbackScopes(body: unknown): string[] {
|
|
146
|
+
const b = (body ?? {}) as { entries?: unknown; counts?: unknown };
|
|
147
|
+
const words: string[] = [];
|
|
148
|
+
if (Array.isArray(b.entries) && b.entries.length > 0) words.push("agent");
|
|
149
|
+
if (b.counts !== null && typeof b.counts === "object" && !Array.isArray(b.counts) && Object.keys(b.counts).length > 0) words.push("passive");
|
|
150
|
+
return words;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** `feedback`: the box's agents' feedback (studio#257, hud RFC-009). */
|
|
154
|
+
export const FEEDBACK_CAPABILITY: BrokerCapabilitySpec = {
|
|
155
|
+
name: "feedback",
|
|
156
|
+
routes: [BROKER_ROUTES.feedback],
|
|
157
|
+
scopeWords: {
|
|
158
|
+
agent: "the batch carries entries the box's agents wrote",
|
|
159
|
+
passive: "the batch carries counts of refusals, errors and restarts",
|
|
160
|
+
},
|
|
161
|
+
scopes({ method, path, body }) {
|
|
162
|
+
if (path !== BROKER_ROUTES.feedback || method !== "POST") return null;
|
|
163
|
+
return feedbackScopes(body);
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
/** `fountain`: Fountain's API, on the operator's key, for the box's own conversations, sandboxes and vault. */
|
|
168
|
+
export const FOUNTAIN_CAPABILITY: BrokerCapabilitySpec = {
|
|
169
|
+
name: "fountain",
|
|
170
|
+
routes: [BROKER_ROUTES.fountain],
|
|
171
|
+
scopeWords: {
|
|
172
|
+
agent: "start a conversation with the box's own agent (POST /fountain/api/conversations)",
|
|
173
|
+
conversations: "read and continue the box's own conversations (/fountain/api/conversations/...)",
|
|
174
|
+
sandboxes: "the box's own sandboxes (/fountain/api/sandboxes/...)",
|
|
175
|
+
vault: "the box's own vault (/fountain/api/vaults/...)",
|
|
176
|
+
},
|
|
177
|
+
scopes({ method, path }) {
|
|
178
|
+
const rest = under(path, BROKER_ROUTES.fountain);
|
|
179
|
+
if (rest === null) return null;
|
|
180
|
+
const family = /^\/api\/(conversations|sandboxes|vaults)(\/|$)/.exec(rest)?.[1];
|
|
181
|
+
if (family === "conversations") return method === "POST" && rest === "/api/conversations" ? ["agent"] : ["conversations"];
|
|
182
|
+
if (family === "sandboxes") return ["sandboxes"];
|
|
183
|
+
if (family === "vaults") return ["vault"];
|
|
184
|
+
return null;
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
/** The standard capabilities, by name. */
|
|
189
|
+
export const STANDARD_CAPABILITIES: Readonly<Record<string, BrokerCapabilitySpec>> = {
|
|
190
|
+
inference: INFERENCE_CAPABILITY,
|
|
191
|
+
egress: EGRESS_CAPABILITY,
|
|
192
|
+
feedback: FEEDBACK_CAPABILITY,
|
|
193
|
+
fountain: FOUNTAIN_CAPABILITY,
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
/** A capability as a broker keeps it from a report. */
|
|
197
|
+
export interface KeptCapability {
|
|
198
|
+
name: string;
|
|
199
|
+
broker: string;
|
|
200
|
+
scope: string[];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Whose credential pays for a box's model calls (#3474, ws-098): `shared`, a
|
|
205
|
+
* credential neither the box's owner nor its visitor owns (a house or
|
|
206
|
+
* operator key); `owner`, the box owner's own; `visitor`, the credential a
|
|
207
|
+
* person brought for this box. `principal` is the payer in ws-080's form
|
|
208
|
+
* (`github:<login>`) when the broker knows them. A broker that does not say
|
|
209
|
+
* leaves it out, and a box reads that as unknown.
|
|
210
|
+
*/
|
|
211
|
+
export interface BrokerPayer {
|
|
212
|
+
kind: "shared" | "owner" | "visitor";
|
|
213
|
+
principal?: string | null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** The payer kinds, in the protocol's order. */
|
|
217
|
+
export const PAYER_KINDS = ["shared", "owner", "visitor"] as const;
|
|
218
|
+
|
|
219
|
+
/** The response header that carries the payer on each answer from `/llm/anthropic` and `/decide`: `<kind>` or `<kind> <principal>`. */
|
|
220
|
+
export const PAYER_HEADER = "chant-payer";
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* The request header a box sends on an inference or decide request made on a
|
|
224
|
+
* person's turn (#3477, ws-099): a grant the broker itself issued to that
|
|
225
|
+
* person for this box, which the person's browser carried to the box's
|
|
226
|
+
* surface. Opaque to chant and to the box. The broker checks its own grant
|
|
227
|
+
* (issued by it, for the box whose token the request carries, not expired or
|
|
228
|
+
* revoked, the person still holding a credential), spends the person's
|
|
229
|
+
* credential, and answers `chant-payer: visitor <principal>`. A grant it does
|
|
230
|
+
* not accept is ignored, not refused: the request is paid as it would have
|
|
231
|
+
* been without one, and the payer header says so.
|
|
232
|
+
*/
|
|
233
|
+
export const GRANT_HEADER = "chant-grant";
|
|
234
|
+
|
|
235
|
+
/** The longest grant a box sends, in characters; a broker may refuse a longer header as malformed. */
|
|
236
|
+
export const GRANT_MAX_LENGTH = 2048;
|
|
237
|
+
|
|
238
|
+
/** Whether `value` is a grant a box may send: printable ASCII without spaces, at most {@link GRANT_MAX_LENGTH} long. */
|
|
239
|
+
export function isGrantValue(value: unknown): value is string {
|
|
240
|
+
return typeof value === "string" && value.length > 0 && value.length <= GRANT_MAX_LENGTH && /^[\x21-\x7e]+$/.test(value);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** A payer as the header carries it. */
|
|
244
|
+
export function formatPayerHeader(payer: BrokerPayer): string {
|
|
245
|
+
return payer.principal ? `${payer.kind} ${payer.principal}` : payer.kind;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** The payer a `chant-payer` header value names, or undefined when it names none the protocol knows. */
|
|
249
|
+
export function parsePayerHeader(value: string | null | undefined): BrokerPayer | undefined {
|
|
250
|
+
const m = /^\s*(shared|owner|visitor)(?:\s+(\S{1,200}))?\s*$/.exec(value ?? "");
|
|
251
|
+
if (!m) return undefined;
|
|
252
|
+
return { kind: m[1] as BrokerPayer["kind"], principal: m[2] ?? null };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** The payer in a `declarationKept` answer (or any object with a `payer` field), or undefined when it has none or a malformed one. */
|
|
256
|
+
export function payerOf(body: unknown): BrokerPayer | undefined {
|
|
257
|
+
const payer = (body as { payer?: unknown } | null)?.payer as { kind?: unknown; principal?: unknown } | undefined;
|
|
258
|
+
if (!payer || typeof payer !== "object" || !PAYER_KINDS.includes(payer.kind as BrokerPayer["kind"])) return undefined;
|
|
259
|
+
const principal = payer.principal;
|
|
260
|
+
if (principal !== undefined && principal !== null && (typeof principal !== "string" || !/^\S{1,200}$/.test(principal))) return undefined;
|
|
261
|
+
return { kind: payer.kind as BrokerPayer["kind"], principal: (principal as string | null | undefined) ?? null };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** What a broker keeps of a box's last report, and, when it says, who pays (#3474). */
|
|
265
|
+
export interface KeptDeclaration {
|
|
266
|
+
capabilities: KeptCapability[];
|
|
267
|
+
at: string;
|
|
268
|
+
payer?: BrokerPayer;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* A report's body, checked, kept to the entries that name `broker`; or the
|
|
273
|
+
* reason it is refused with a 400. The same rules as studio's lobby
|
|
274
|
+
* (`lobby/capabilities.mjs`).
|
|
275
|
+
*/
|
|
276
|
+
export function parseDeclarationReport(body: unknown, broker: string): { capabilities: KeptCapability[] } | { error: string } {
|
|
277
|
+
const list = (body as { capabilities?: unknown } | null)?.capabilities;
|
|
278
|
+
if (!Array.isArray(list)) return { error: 'The body is { "capabilities": [{ "name", "broker", "scope" }] }.' };
|
|
279
|
+
if (list.length > DECLARATION_LIMITS.capabilities) return { error: `At most ${DECLARATION_LIMITS.capabilities} capabilities.` };
|
|
280
|
+
const kept = new Map<string, KeptCapability>();
|
|
281
|
+
for (const c of list as { name?: unknown; broker?: unknown; scope?: unknown }[]) {
|
|
282
|
+
if (!c || typeof c.name !== "string" || !CAPABILITY_NAME.test(c.name)) return { error: `${JSON.stringify(c?.name)} is not a capability name.` };
|
|
283
|
+
if (c.broker !== broker) continue;
|
|
284
|
+
const scope = Array.isArray(c.scope) ? c.scope : [];
|
|
285
|
+
if (scope.length > DECLARATION_LIMITS.scopeWords || scope.some((w) => typeof w !== "string" || w.length === 0 || w.length > DECLARATION_LIMITS.wordLength)) {
|
|
286
|
+
return { error: `${c.name}'s scope is at most ${DECLARATION_LIMITS.scopeWords} words of 1 to ${DECLARATION_LIMITS.wordLength} characters.` };
|
|
287
|
+
}
|
|
288
|
+
const seen = kept.get(c.name) ?? { name: c.name, broker, scope: [] };
|
|
289
|
+
for (const w of scope as string[]) if (!seen.scope.includes(w)) seen.scope.push(w);
|
|
290
|
+
kept.set(c.name, seen);
|
|
291
|
+
}
|
|
292
|
+
return { capabilities: [...kept.values()] };
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Why `declared` (the box's last report, or undefined when it never sent
|
|
297
|
+
* one) does not allow scope word `word` of `capability` through `broker`,
|
|
298
|
+
* naming the entry that would; null when it does.
|
|
299
|
+
*/
|
|
300
|
+
export function declaredRefusal(declared: { capabilities?: readonly KeptCapability[] } | undefined, broker: string, capability: string, word: string): string | null {
|
|
301
|
+
const entry = `{ "name": "${capability}", "broker": "${broker}", "scope": ["${word}"] }`;
|
|
302
|
+
if (!declared) return `The broker has no declaration from this box yet, so it brokers nothing for it, and ${capability} ${word} needs ${entry} in the box block of its chant.workspace.json, reported to ${BROKER_ROUTES.declaration}.`;
|
|
303
|
+
const found = declared.capabilities?.find((c) => c.name === capability && c.broker === broker);
|
|
304
|
+
if (!found) return `This box declares no ${capability} capability brokered by ${broker}. Add ${entry} to the box block in its chant.workspace.json.`;
|
|
305
|
+
if (!found.scope.includes(word)) {
|
|
306
|
+
return `This box's ${capability} capability does not list ${word} in its scope (it lists ${found.scope.length ? found.scope.join(", ") : "nothing"}). Add "${word}" to that scope in the box block of its chant.workspace.json.`;
|
|
307
|
+
}
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** The message of a refusal body (`{ error: string }` or `{ error: { message } }`), or undefined when it is not one. */
|
|
312
|
+
export function refusalMessage(body: unknown): string | undefined {
|
|
313
|
+
const error = (body as { error?: unknown } | null)?.error;
|
|
314
|
+
if (typeof error === "string" && error !== "") return error;
|
|
315
|
+
const message = (error as { message?: unknown } | null)?.message;
|
|
316
|
+
return typeof message === "string" && message !== "" ? message : undefined;
|
|
317
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #3152, ws-094: the factory declares which builder agent builds at which
|
|
3
|
+
* tier, per member kind, replacing studio's studio/role and studio/tier Agent
|
|
4
|
+
* metadata. The declaration read refuses an ambiguous tier, tiers without a
|
|
5
|
+
* builders member and a session no agent declares; status --json and
|
|
6
|
+
* graph --json print the tiers and builderFor, the agent for each built
|
|
7
|
+
* member and tier, so a slice-tier answer resolves without studio.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
|
11
|
+
import { cleanScratch, contract, git, repo } from "./__fixtures__/contract-repo";
|
|
12
|
+
import { builderTable } from "./box-factory";
|
|
13
|
+
import { parseDeclaration, WorkspaceReadError, type Declaration } from "./declaration";
|
|
14
|
+
import { workspaceGraph } from "./graph-cli";
|
|
15
|
+
import graphSchema from "./graph.schema.json";
|
|
16
|
+
import { workspaceStatus } from "./status";
|
|
17
|
+
import statusSchema from "./status.schema.json";
|
|
18
|
+
|
|
19
|
+
afterAll(cleanScratch);
|
|
20
|
+
|
|
21
|
+
type Json = Record<string, unknown>;
|
|
22
|
+
|
|
23
|
+
const TIERS = [
|
|
24
|
+
{ tier: "small", agent: "builder-small" },
|
|
25
|
+
{ tier: "medium", agent: "builder-medium" },
|
|
26
|
+
{ tier: "large", agent: "builder-large" },
|
|
27
|
+
{ tier: "small", agent: "infra-small", kinds: ["terraform"], session: "infra" },
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
function declaration(factory: Json = {}, agents: Json[] = [{ name: "infra", member: "network" }]): Json {
|
|
31
|
+
return {
|
|
32
|
+
name: "acme",
|
|
33
|
+
schema: 1,
|
|
34
|
+
members: [
|
|
35
|
+
{ name: "app", dir: "app", kind: "other", because: "the app" },
|
|
36
|
+
{ name: "network", dir: "network", kind: "other", because: "an estate" },
|
|
37
|
+
{ name: "delivery", dir: "delivery", kind: "other", because: "declares the agents" },
|
|
38
|
+
{
|
|
39
|
+
name: "box",
|
|
40
|
+
dir: "box",
|
|
41
|
+
kind: "other",
|
|
42
|
+
because: "the box",
|
|
43
|
+
box: { services: [{ name: "app", cmd: "node app.mjs" }], factory: { builds: ["app", "network"], builders: "delivery", tiers: TIERS, ...factory } },
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
agents,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const parse = (doc: Json): Declaration => parseDeclaration(JSON.stringify(doc, null, 2), "chant.workspace.json");
|
|
51
|
+
|
|
52
|
+
function readFailure(doc: Json): { code: string; message: string } {
|
|
53
|
+
try {
|
|
54
|
+
parse(doc);
|
|
55
|
+
} catch (err) {
|
|
56
|
+
if (err instanceof WorkspaceReadError) return { code: err.code, message: err.message };
|
|
57
|
+
throw err;
|
|
58
|
+
}
|
|
59
|
+
throw new Error("the declaration read");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
describe("the factory's tiers (#3152)", () => {
|
|
63
|
+
test("read in file order, kinds and session null when not declared", () => {
|
|
64
|
+
const f = parse(declaration()).members[3].box!.factory!;
|
|
65
|
+
expect(f.tiers).toEqual([
|
|
66
|
+
{ tier: "small", agent: "builder-small", kinds: null, session: null },
|
|
67
|
+
{ tier: "medium", agent: "builder-medium", kinds: null, session: null },
|
|
68
|
+
{ tier: "large", agent: "builder-large", kinds: null, session: null },
|
|
69
|
+
{ tier: "small", agent: "infra-small", kinds: ["terraform"], session: "infra" },
|
|
70
|
+
]);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("a factory without tiers reads as before, with none", () => {
|
|
74
|
+
expect(parse(declaration({ tiers: undefined })).members[3].box!.factory!.tiers).toEqual([]);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("two entries for one tier and kind, or two defaults for one tier, can't be read", () => {
|
|
78
|
+
const twice = readFailure(declaration({ tiers: [...TIERS, { tier: "small", agent: "other-small" }] }));
|
|
79
|
+
expect(twice.code).toBe("declaration-invalid");
|
|
80
|
+
expect(twice.message).toMatch(/tiers\/0 and tiers\/4 both name a builder at tier small with no kinds/);
|
|
81
|
+
const kind = readFailure(declaration({ tiers: [...TIERS, { tier: "small", agent: "x", kinds: ["helm", "terraform"] }] }));
|
|
82
|
+
expect(kind.message).toMatch(/tiers\/3 and tiers\/4 both name a builder at tier small for kind terraform/);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("tiers need the builders member, a session must be declared, and an entry needs a tier and an agent", () => {
|
|
86
|
+
expect(readFailure(declaration({ builders: undefined })).message).toMatch(/declares tiers and no builders/);
|
|
87
|
+
expect(readFailure(declaration({}, [])).message).toMatch(/tiers\/3 names the session "infra", which is not a declared agent session/);
|
|
88
|
+
expect(readFailure(declaration({ tiers: [{ tier: "small" }] })).code).toBe("declaration-invalid");
|
|
89
|
+
expect(readFailure(declaration({ tiers: [{ tier: "Small!", agent: "a" }] })).code).toBe("declaration-invalid");
|
|
90
|
+
expect(readFailure(declaration({ tiers: [] })).code).toBe("declaration-invalid");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("builderFor picks the entry for the member's kind, else the tier's default, and leaves out a tier with neither", () => {
|
|
94
|
+
const f = parse(declaration({ tiers: [{ tier: "small", agent: "builder-small" }, { tier: "large", agent: "infra-large", kinds: ["terraform"] }] })).members[3].box!.factory!;
|
|
95
|
+
expect(builderTable(f, [{ name: "app", kind: "app" }, { name: "network", kind: "terraform" }])).toEqual({
|
|
96
|
+
app: { small: { agent: "builder-small", session: null } },
|
|
97
|
+
network: { small: { agent: "builder-small", session: null }, large: { agent: "infra-large", session: null } },
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe("the read contract (#3152)", () => {
|
|
103
|
+
const status = contract(statusSchema);
|
|
104
|
+
const graph = contract(graphSchema);
|
|
105
|
+
let root: string;
|
|
106
|
+
|
|
107
|
+
beforeAll(() => {
|
|
108
|
+
const doc = declaration();
|
|
109
|
+
// The estate member is a Terraform root by its kind: give it the terraform kind through a local plugin.
|
|
110
|
+
(doc.members as Json[])[1] = { name: "network", dir: "network", kind: "terraform" };
|
|
111
|
+
doc.pins = [{ path: "plugins/kinds" }];
|
|
112
|
+
root = repo({
|
|
113
|
+
"chant.workspace.json": `${JSON.stringify(doc, null, 2)}\n`,
|
|
114
|
+
"plugins/kinds/package.json": JSON.stringify({ name: "kinds", version: "1.0.0", exports: { "./workspace-kinds": "./k.json" } }),
|
|
115
|
+
"plugins/kinds/k.json": JSON.stringify({ schema: 1, kinds: [{ name: "terraform", description: "a Terraform root", precedence: 400, probe: { anyFile: ["main.tf"] } }] }),
|
|
116
|
+
"app/README.md": "the app\n",
|
|
117
|
+
"network/main.tf": "\n",
|
|
118
|
+
"delivery/README.md": "the agents\n",
|
|
119
|
+
"box/README.md": "the box\n",
|
|
120
|
+
});
|
|
121
|
+
git(root, "add", "-A");
|
|
122
|
+
git(root, "commit", "-q", "-m", "the workspace");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const expected = {
|
|
126
|
+
app: { small: { agent: "builder-small", session: null }, medium: { agent: "builder-medium", session: null }, large: { agent: "builder-large", session: null } },
|
|
127
|
+
network: { small: { agent: "infra-small", session: "infra" }, medium: { agent: "builder-medium", session: null }, large: { agent: "builder-large", session: null } },
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
test("status --json prints the tiers and builderFor under the box's factory", async () => {
|
|
131
|
+
const out = await workspaceStatus({ cwd: root, env: "prod" });
|
|
132
|
+
status.expectValid(out);
|
|
133
|
+
if ("error" in out) throw new Error(out.error.message);
|
|
134
|
+
const factory = out.members.find((m) => m.name === "box")!.box!.factory!;
|
|
135
|
+
expect(factory.tiers).toHaveLength(4);
|
|
136
|
+
expect(factory.builderFor).toEqual(expected);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("graph --json prints the same on the box member", async () => {
|
|
140
|
+
const { doc: out } = await workspaceGraph({ cwd: root });
|
|
141
|
+
graph.expectValid(out);
|
|
142
|
+
if ("error" in out) throw new Error(out.error.message);
|
|
143
|
+
const box = out.members.find((m) => m.name === "box")!.box!;
|
|
144
|
+
expect(box.factory?.builderFor).toEqual(expected);
|
|
145
|
+
});
|
|
146
|
+
});
|