@intentius/chant 0.91.0 → 0.94.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/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/handlers/graph.d.ts.map +1 -1
- package/dist/cli/handlers/operator.d.ts +6 -0
- package/dist/cli/handlers/operator.d.ts.map +1 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/workspace-plugins.d.ts +6 -6
- package/dist/cli/mcp/workspace-tools.d.ts +2 -1
- package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
- package/dist/cli/plugins.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +12 -2
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
- package/dist/config.d.ts +22 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/lexicon-module.d.ts +22 -0
- package/dist/lexicon-module.d.ts.map +1 -1
- package/dist/lifecycle/converge-ledger.d.ts +19 -0
- package/dist/lifecycle/converge-ledger.d.ts.map +1 -1
- package/dist/lifecycle/run-ledger.d.ts +2 -0
- package/dist/lifecycle/run-ledger.d.ts.map +1 -1
- package/dist/lifecycle/symptoms.d.ts +51 -0
- package/dist/lifecycle/symptoms.d.ts.map +1 -1
- package/dist/lint/rules/index.d.ts +4 -2
- package/dist/lint/rules/index.d.ts.map +1 -1
- package/dist/lint/rules/op/index.d.ts +14 -3
- package/dist/lint/rules/op/index.d.ts.map +1 -1
- package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts.map +1 -1
- package/dist/lint/rules/op/sys010-key-over-plain-http.d.ts +29 -0
- package/dist/lint/rules/op/sys010-key-over-plain-http.d.ts.map +1 -0
- package/dist/lint/rules/sys001-no-literal-key.d.ts +3 -0
- package/dist/lint/rules/sys001-no-literal-key.d.ts.map +1 -0
- package/dist/op/__fixtures__/decide-stub-backend.d.ts +53 -0
- package/dist/op/__fixtures__/decide-stub-backend.d.ts.map +1 -0
- package/dist/op/activities/activity-contracts.d.ts +201 -0
- package/dist/op/activities/activity-contracts.d.ts.map +1 -1
- package/dist/op/activities/apply.d.ts.map +1 -1
- package/dist/op/activities/change-coverage.d.ts +44 -0
- package/dist/op/activities/change-coverage.d.ts.map +1 -0
- package/dist/op/activities/converge.d.ts +24 -1
- package/dist/op/activities/converge.d.ts.map +1 -1
- package/dist/op/activities/decide.d.ts +107 -0
- package/dist/op/activities/decide.d.ts.map +1 -0
- package/dist/op/activities/index.d.ts +10 -0
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activities/shell.d.ts +32 -0
- package/dist/op/activities/shell.d.ts.map +1 -1
- package/dist/op/activities/source-release.d.ts +86 -0
- package/dist/op/activities/source-release.d.ts.map +1 -0
- package/dist/op/activities/source-rollback.d.ts +100 -0
- package/dist/op/activities/source-rollback.d.ts.map +1 -0
- package/dist/op/activities/work-evidence.d.ts +42 -0
- package/dist/op/activities/work-evidence.d.ts.map +1 -0
- package/dist/op/activity-contract.d.ts +1 -0
- package/dist/op/activity-contract.d.ts.map +1 -1
- package/dist/op/activity-profiles.d.ts +16 -0
- package/dist/op/activity-profiles.d.ts.map +1 -1
- package/dist/op/builders.d.ts +84 -2
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/composites/converge-op.d.ts +31 -4
- package/dist/op/composites/converge-op.d.ts.map +1 -1
- package/dist/op/decide-backend.d.ts +116 -0
- package/dist/op/decide-backend.d.ts.map +1 -0
- package/dist/op/decide-config.d.ts +120 -0
- package/dist/op/decide-config.d.ts.map +1 -0
- package/dist/op/decide-read-inputs.d.ts +40 -0
- package/dist/op/decide-read-inputs.d.ts.map +1 -0
- package/dist/op/gate-wait.d.ts +43 -0
- package/dist/op/gate-wait.d.ts.map +1 -0
- package/dist/op/gate.d.ts +5 -2
- package/dist/op/gate.d.ts.map +1 -1
- package/dist/op/index.d.ts +11 -4
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/local-executor.d.ts +29 -4
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/local-output.d.ts +2 -0
- package/dist/op/local-output.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 +97 -1
- package/dist/op/operator.d.ts.map +1 -1
- package/dist/op/runtime.d.ts +37 -5
- package/dist/op/runtime.d.ts.map +1 -1
- package/dist/op/runtimes/local.d.ts.map +1 -1
- package/dist/op/source-archive.d.ts +63 -0
- package/dist/op/source-archive.d.ts.map +1 -0
- package/dist/op/steward-points.d.ts +136 -0
- package/dist/op/steward-points.d.ts.map +1 -0
- package/dist/op/steward-turn.d.ts +53 -0
- package/dist/op/steward-turn.d.ts.map +1 -0
- package/dist/op/steward.d.ts +22 -1
- package/dist/op/steward.d.ts.map +1 -1
- package/dist/op/types.d.ts +7 -0
- package/dist/op/types.d.ts.map +1 -1
- package/dist/op/work-lease-run.d.ts +1 -1
- package/dist/op/work-lease-run.d.ts.map +1 -1
- package/dist/workspace/__fixtures__/decide-workspace.d.ts +100 -0
- package/dist/workspace/__fixtures__/decide-workspace.d.ts.map +1 -0
- package/dist/workspace/answers-ledger.d.ts +76 -0
- package/dist/workspace/answers-ledger.d.ts.map +1 -0
- package/dist/workspace/box-intent.d.ts +85 -0
- package/dist/workspace/box-intent.d.ts.map +1 -0
- package/dist/workspace/changes-cli.d.ts +16 -0
- package/dist/workspace/changes-cli.d.ts.map +1 -0
- package/dist/workspace/changes.d.ts +200 -0
- package/dist/workspace/changes.d.ts.map +1 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-fly-site.d.ts +59 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-fly-site.d.ts.map +1 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-live-names.d.ts +51 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-live-names.d.ts.map +1 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-rollback.d.ts +26 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-rollback.d.ts.map +1 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.d.ts +59 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.d.ts.map +1 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-policy.d.ts +22 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-policy.d.ts.map +1 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts +71 -0
- package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts.map +1 -0
- package/dist/workspace/chant-migrations.d.ts +104 -0
- package/dist/workspace/chant-migrations.d.ts.map +1 -0
- package/dist/workspace/checks/boxes.d.ts +47 -1
- package/dist/workspace/checks/boxes.d.ts.map +1 -1
- package/dist/workspace/checks/diagrams.d.ts +31 -0
- package/dist/workspace/checks/diagrams.d.ts.map +1 -0
- package/dist/workspace/checks/records.d.ts +7 -0
- package/dist/workspace/checks/records.d.ts.map +1 -1
- package/dist/workspace/checks.d.ts +7 -0
- package/dist/workspace/checks.d.ts.map +1 -1
- package/dist/workspace/compose-graph.d.ts +2 -0
- package/dist/workspace/compose-graph.d.ts.map +1 -1
- package/dist/workspace/conformance/index.d.ts.map +1 -1
- package/dist/workspace/decide.d.ts +49 -4
- package/dist/workspace/decide.d.ts.map +1 -1
- package/dist/workspace/decision-points.schema.json +3 -3
- package/dist/workspace/declaration.d.ts +58 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.schema.json +119 -1
- package/dist/workspace/declared-kinds.d.ts +20 -0
- package/dist/workspace/declared-kinds.d.ts.map +1 -1
- package/dist/workspace/generated-files.d.ts +7 -0
- package/dist/workspace/generated-files.d.ts.map +1 -1
- package/dist/workspace/intent-joins.d.ts +5 -5
- package/dist/workspace/intent.d.ts.map +1 -1
- package/dist/workspace/lineage-check.d.ts +3 -0
- package/dist/workspace/lineage-check.d.ts.map +1 -1
- package/dist/workspace/lineage-upgrade.d.ts +6 -1
- package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
- package/dist/workspace/ls.d.ts +39 -1
- package/dist/workspace/ls.d.ts.map +1 -1
- package/dist/workspace/point-answer.schema.json +95 -0
- package/dist/workspace/points-cli.d.ts +2 -0
- package/dist/workspace/points-cli.d.ts.map +1 -1
- package/dist/workspace/points.d.ts +4 -6
- package/dist/workspace/points.d.ts.map +1 -1
- package/dist/workspace/reason-codes.d.ts +16 -2
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/records-cli.d.ts +11 -1
- package/dist/workspace/records-cli.d.ts.map +1 -1
- package/dist/workspace/records-close.d.ts +1 -1
- package/dist/workspace/records-close.d.ts.map +1 -1
- package/dist/workspace/records-write.d.ts +11 -5
- package/dist/workspace/records-write.d.ts.map +1 -1
- package/dist/workspace/records.d.ts +34 -4
- package/dist/workspace/records.d.ts.map +1 -1
- package/dist/workspace/status-gates.d.ts +7 -2
- package/dist/workspace/status-gates.d.ts.map +1 -1
- package/dist/workspace/status-stewards.d.ts +65 -1
- package/dist/workspace/status-stewards.d.ts.map +1 -1
- package/dist/workspace/status.d.ts +24 -0
- package/dist/workspace/status.d.ts.map +1 -1
- package/dist/workspace/work-cli.d.ts +113 -2
- package/dist/workspace/work-cli.d.ts.map +1 -1
- package/dist/workspace/work-evidence.d.ts +74 -0
- package/dist/workspace/work-evidence.d.ts.map +1 -0
- package/dist/workspace/work.d.ts +43 -1
- package/dist/workspace/work.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/cli/commands/audit.test.ts +9 -1
- package/src/cli/commands/build.ts +3 -2
- package/src/cli/commands/lint.ts +7 -1
- package/src/cli/handlers/graph.test.ts +20 -0
- package/src/cli/handlers/graph.ts +16 -2
- package/src/cli/handlers/operator.ts +34 -2
- package/src/cli/handlers/run.test.ts +154 -2
- package/src/cli/handlers/run.ts +121 -2
- package/src/cli/main.test.ts +10 -0
- package/src/cli/main.ts +23 -3
- package/src/cli/mcp/workspace-plugins.ts +6 -6
- package/src/cli/mcp/workspace-tools.test.ts +35 -19
- package/src/cli/mcp/workspace-tools.ts +62 -4
- package/src/cli/plugins.ts +11 -3
- package/src/cli/registry.ts +12 -2
- package/src/cli/serve-mcp-workspace.test.ts +6 -6
- package/src/codegen/pinned-upgrade.ts +2 -1
- package/src/codegen/release-wiring.test.ts +49 -2
- package/src/components/capability-plugin-loader.ts +3 -3
- package/src/components/cli-support.ts +2 -2
- package/src/config.ts +11 -0
- package/src/lexicon-module.ts +56 -1
- package/src/lexicon-package.test.ts +96 -0
- package/src/lifecycle/converge-ledger.ts +20 -0
- package/src/lifecycle/run-ledger.ts +6 -0
- package/src/lifecycle/symptoms.ts +95 -0
- package/src/lint/rules/index.ts +6 -2
- package/src/lint/rules/op/index.ts +17 -4
- package/src/lint/rules/op/ops014-converge-rule-refusals.test.ts +23 -1
- package/src/lint/rules/op/ops014-converge-rule-refusals.ts +10 -6
- package/src/lint/rules/op/sys010-key-over-plain-http.test.ts +52 -0
- package/src/lint/rules/op/sys010-key-over-plain-http.ts +100 -0
- package/src/lint/rules/sys001-no-literal-key.test.ts +31 -0
- package/src/lint/rules/sys001-no-literal-key.ts +67 -0
- package/src/op/__fixtures__/decide-stub-backend.ts +143 -0
- package/src/op/activities/activity-contracts.ts +190 -1
- package/src/op/activities/apply.ts +7 -6
- package/src/op/activities/change-coverage.ts +60 -0
- package/src/op/activities/converge.test.ts +55 -1
- package/src/op/activities/converge.ts +208 -29
- package/src/op/activities/decide.live.test.ts +78 -0
- package/src/op/activities/decide.test.ts +308 -0
- package/src/op/activities/decide.ts +267 -0
- package/src/op/activities/index.ts +21 -0
- package/src/op/activities/shell.test.ts +91 -1
- package/src/op/activities/shell.ts +91 -2
- package/src/op/activities/source-release.test.ts +118 -0
- package/src/op/activities/source-release.ts +168 -0
- package/src/op/activities/source-rollback.test.ts +131 -0
- package/src/op/activities/source-rollback.ts +239 -0
- package/src/op/activities/work-evidence.ts +62 -0
- package/src/op/activity-contract-registry.test.ts +2 -0
- package/src/op/activity-contract-registry.ts +2 -2
- package/src/op/activity-contract.test.ts +12 -0
- package/src/op/activity-contract.ts +6 -1
- package/src/op/activity-profiles.ts +29 -0
- package/src/op/activity-registry.test.ts +1 -1
- package/src/op/activity-registry.ts +2 -2
- package/src/op/builders.ts +119 -5
- package/src/op/command-gate.test.ts +192 -0
- package/src/op/composites/converge-op.test.ts +27 -1
- package/src/op/composites/converge-op.ts +69 -15
- package/src/op/decide-backend.test.ts +44 -0
- package/src/op/decide-backend.ts +212 -0
- package/src/op/decide-config.test.ts +52 -0
- package/src/op/decide-config.ts +80 -0
- package/src/op/decide-read-inputs.ts +117 -0
- package/src/op/gate-approval.ts +2 -2
- package/src/op/gate-wait.ts +57 -0
- package/src/op/gate.ts +8 -3
- package/src/op/generate-pipeline.ts +2 -2
- package/src/op/index.ts +18 -3
- package/src/op/local-executor.ts +151 -15
- package/src/op/local-output.test.ts +33 -0
- package/src/op/local-output.ts +20 -2
- package/src/op/op-ir.test.ts +10 -0
- package/src/op/op-ir.ts +4 -0
- package/src/op/op-verb-class.ts +1 -0
- package/src/op/operator.ts +290 -11
- package/src/op/runtime.ts +31 -8
- package/src/op/runtimes/local.ts +3 -1
- package/src/op/source-archive.ts +161 -0
- package/src/op/steward-points.test.ts +364 -0
- package/src/op/steward-points.ts +194 -0
- package/src/op/steward-turn.ts +79 -0
- package/src/op/steward.test.ts +87 -3
- package/src/op/steward.ts +25 -1
- package/src/op/types.ts +7 -0
- package/src/op/work-lease-run.test.ts +20 -1
- package/src/op/work-lease-run.ts +2 -2
- package/src/workspace/__fixtures__/decide-workspace.ts +129 -0
- package/src/workspace/__fixtures__/studio-template-84c8b21.json +67 -0
- package/src/workspace/answers-ledger.ts +118 -0
- package/src/workspace/box-intent.test.ts +205 -0
- package/src/workspace/box-intent.ts +159 -0
- package/src/workspace/changes-cli.ts +56 -0
- package/src/workspace/changes.schema.json +472 -0
- package/src/workspace/changes.test.ts +315 -0
- package/src/workspace/changes.ts +512 -0
- package/src/workspace/chant-migrations/chud-lexicon-exit-fly-site.ts +234 -0
- package/src/workspace/chant-migrations/chud-lexicon-exit-live-names.ts +164 -0
- package/src/workspace/chant-migrations/chud-lexicon-exit-rollback.ts +223 -0
- package/src/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.ts +195 -0
- package/src/workspace/chant-migrations/chud-lexicon-exit-ship-policy.ts +108 -0
- package/src/workspace/chant-migrations/chud-lexicon-exit.ts +882 -0
- package/src/workspace/chant-migrations.e2e.test.ts +1089 -0
- package/src/workspace/chant-migrations.ts +169 -0
- package/src/workspace/check-contract.test.ts +4 -2
- package/src/workspace/check.schema.json +19 -5
- package/src/workspace/checks/boxes.test.ts +82 -2
- package/src/workspace/checks/boxes.ts +202 -1
- package/src/workspace/checks/diagrams.test.ts +134 -0
- package/src/workspace/checks/diagrams.ts +108 -0
- package/src/workspace/checks/records.ts +47 -0
- package/src/workspace/checks.ts +20 -2
- package/src/workspace/compose-graph.ts +2 -0
- package/src/workspace/conformance/__fixture__/decisions/decision.kind.mjs +10 -0
- package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +26 -0
- package/src/workspace/conformance/index.ts +5 -0
- package/src/workspace/decide.test.ts +1 -1
- package/src/workspace/decide.ts +156 -23
- package/src/workspace/decision-points.schema.json +3 -3
- package/src/workspace/declaration.schema.json +119 -1
- package/src/workspace/declaration.test.ts +58 -0
- package/src/workspace/declaration.ts +112 -0
- package/src/workspace/declared-kinds.test.ts +31 -5
- package/src/workspace/declared-kinds.ts +46 -1
- package/src/workspace/generated-files.ts +14 -0
- package/src/workspace/graph-cli.ts +1 -1
- package/src/workspace/graph.schema.json +9 -1
- package/src/workspace/intent-joins.test.ts +5 -5
- package/src/workspace/intent-joins.ts +5 -5
- package/src/workspace/intent.schema.json +8 -2
- package/src/workspace/intent.ts +3 -10
- package/src/workspace/lineage-check.ts +16 -1
- package/src/workspace/lineage-upgrade-cli.ts +1 -1
- package/src/workspace/lineage-upgrade.test.ts +70 -0
- package/src/workspace/lineage-upgrade.ts +81 -10
- package/src/workspace/ls-contract.test.ts +57 -0
- package/src/workspace/ls.schema.json +46 -1
- package/src/workspace/ls.ts +62 -3
- package/src/workspace/points-cli.ts +17 -3
- package/src/workspace/points-write.schema.json +11 -1
- package/src/workspace/points.schema.json +51 -0
- package/src/workspace/points.test.ts +16 -15
- package/src/workspace/points.ts +5 -17
- package/src/workspace/read-contract.test.ts +9 -1
- package/src/workspace/reason-codes.test.ts +18 -8
- package/src/workspace/reason-codes.ts +21 -2
- package/src/workspace/records-amend.schema.json +2 -0
- package/src/workspace/records-cli.ts +27 -4
- package/src/workspace/records-close.schema.json +2 -0
- package/src/workspace/records-formats.test.ts +15 -15
- package/src/workspace/records-new.schema.json +2 -0
- package/src/workspace/records-review.schema.json +2 -0
- package/src/workspace/records-sessions-write.e2e.test.ts +77 -0
- package/src/workspace/records-sessions-write.test.ts +2 -34
- package/src/workspace/records-since.test.ts +8 -7
- package/src/workspace/records-write.e2e.test.ts +108 -0
- package/src/workspace/records-write.test.ts +32 -41
- package/src/workspace/records-write.ts +33 -15
- package/src/workspace/records.schema.json +36 -2
- package/src/workspace/records.test.ts +49 -4
- package/src/workspace/records.ts +109 -3
- package/src/workspace/status-contract.test.ts +14 -8
- package/src/workspace/status-gates.ts +9 -4
- package/src/workspace/status-stewards.ts +155 -5
- package/src/workspace/status.schema.json +129 -8
- package/src/workspace/status.ts +46 -1
- package/src/workspace/work-acceptance.test.ts +274 -0
- package/src/workspace/{work-cli.test.ts → work-cli.e2e.test.ts} +1 -1
- package/src/workspace/work-cli.ts +193 -10
- package/src/workspace/work-evidence.schema.json +212 -0
- package/src/workspace/work-evidence.ts +169 -0
- package/src/workspace/work-history.schema.json +349 -0
- package/src/workspace/work-history.test.ts +156 -0
- package/src/workspace/{work-readiness-chud.test.ts → work-readiness.test.ts} +30 -32
- package/src/workspace/work.test.ts +20 -2
- package/src/workspace/work.ts +100 -1
- /package/src/cli/{discovery-skip.test.ts → discovery-skip.e2e.test.ts} +0 -0
- /package/src/workspace/conformance/{conformance.test.ts → conformance.e2e.test.ts} +0 -0
- /package/src/workspace/{which-chant.test.ts → which-chant.e2e.test.ts} +0 -0
package/src/workspace/records.ts
CHANGED
|
@@ -6,8 +6,10 @@
|
|
|
6
6
|
* record a kind locates, parses its structured core (the front matter, as the
|
|
7
7
|
* JSON subset of YAML, or the whole file as one I-JSON object, ws-053),
|
|
8
8
|
* validates it against the kind's schema and derives supersession from the
|
|
9
|
-
* records' own `supersedes` links
|
|
10
|
-
*
|
|
9
|
+
* records' own `supersedes` links, and remediation from their `remediates`
|
|
10
|
+
* links (#2774): a later record fixing a closed one's consequence without
|
|
11
|
+
* replacing it, which leaves the closed record's status unchanged. It never
|
|
12
|
+
* writes a record; `records-write.ts` does, through the rules here (#2670).
|
|
11
13
|
*
|
|
12
14
|
* A record that fails any of that is still returned, with reason codes, and the
|
|
13
15
|
* read succeeds. Only a failure to read the kind, its schema or the revision is
|
|
@@ -31,7 +33,7 @@ import { joinSessions, type SessionCitation } from "./record-sessions";
|
|
|
31
33
|
import type { RecordSource } from "./record-source";
|
|
32
34
|
import { sourceBlock, sourceBlockProblems, transcriptDrift } from "./source-block";
|
|
33
35
|
import type { WorkspaceTree } from "./tree";
|
|
34
|
-
import type { DecisionWork, WorkLink, WorkWarningCode } from "./work";
|
|
36
|
+
import type { DecisionWork, WorkAcceptance, WorkLink, WorkWarningCode } from "./work";
|
|
35
37
|
import type { AnswerWarningCode } from "./points";
|
|
36
38
|
|
|
37
39
|
// ── Reason codes ─────────────────────────────────────────────────────────────
|
|
@@ -51,6 +53,10 @@ export const RECORD_REASON_CODES = [
|
|
|
51
53
|
"record-supersedes-unknown",
|
|
52
54
|
/** A second closed record supersedes a record another one already superseded. */
|
|
53
55
|
"record-supersedes-conflict",
|
|
56
|
+
/** A `remediates` link names an id no record has (#2774). */
|
|
57
|
+
"record-remediates-unknown",
|
|
58
|
+
/** A `remediates` link names a record that isn't closed: a record still open is amended instead (#2774). */
|
|
59
|
+
"record-remediates-not-closed",
|
|
54
60
|
/** A closed session's seal is not the digest of its text: it changed after it closed (#2673). */
|
|
55
61
|
"session-seal-mismatch",
|
|
56
62
|
/** A session's verdict names a record the kind's subject records do not have (#2673). */
|
|
@@ -279,6 +285,16 @@ export const recordKindSchema = z
|
|
|
279
285
|
* (ws-053). Optional, and only on a kind with states.
|
|
280
286
|
*/
|
|
281
287
|
supersedes: z.object({ field: z.string().min(1), key: z.string().min(1).optional() }).strict().optional(),
|
|
288
|
+
/**
|
|
289
|
+
* The field of links to records this one remediates: a later fix to a
|
|
290
|
+
* closed record's consequence, that leaves the closed record's status
|
|
291
|
+
* unchanged (#2774). Shaped like `supersedes`: with `key`, a list of
|
|
292
|
+
* objects whose `key` holds the target id; without it, one id or a list
|
|
293
|
+
* of ids. Optional, and only on a kind with states. A link is valid only
|
|
294
|
+
* when the target exists and is in one of `closedStates`; a target still
|
|
295
|
+
* open is amended instead, never remediated.
|
|
296
|
+
*/
|
|
297
|
+
remediates: z.object({ field: z.string().min(1), key: z.string().min(1).optional() }).strict().optional(),
|
|
282
298
|
/**
|
|
283
299
|
* How strongly each state is approved (#2524 D4). With it, a supersedes
|
|
284
300
|
* link takes effect when the new record's rank is above 0 and at least the
|
|
@@ -298,6 +314,15 @@ export const recordKindSchema = z
|
|
|
298
314
|
* (#2549). Optional.
|
|
299
315
|
*/
|
|
300
316
|
constrains: z.object({ field: z.string().min(1) }).strict().optional(),
|
|
317
|
+
/**
|
|
318
|
+
* The front-matter list of workspace paths a record puts out of scope
|
|
319
|
+
* (#2773): a file or a directory, from the workspace root. `chant
|
|
320
|
+
* workspace check --changes` reports a change to one as
|
|
321
|
+
* `change-out-of-scope` when the record is in hand for the change, and
|
|
322
|
+
* the record does not cover it even where its constrains would.
|
|
323
|
+
* Optional.
|
|
324
|
+
*/
|
|
325
|
+
outOfScope: z.object({ field: z.string().min(1) }).strict().optional(),
|
|
301
326
|
/**
|
|
302
327
|
* The front-matter list of review verdicts and the field naming the
|
|
303
328
|
* decider (#2671, #2672). With it, each record gets a digest of its text
|
|
@@ -307,6 +332,17 @@ export const recordKindSchema = z
|
|
|
307
332
|
* `withdrawn_on`. Optional.
|
|
308
333
|
*/
|
|
309
334
|
reviews: z.object({ field: z.string().min(1), decider: z.string().min(1) }).strict().optional(),
|
|
335
|
+
/**
|
|
336
|
+
* The front-matter field a new record's proposer is named on, when the
|
|
337
|
+
* kind opts in (#2756). `records new --by` and the MCP `records-new`
|
|
338
|
+
* tool's `by` write it for a record that opens in the kind's first
|
|
339
|
+
* state — a proposal naming who proposed it — instead of
|
|
340
|
+
* `reviews.decider`'s field, which `by` fills only when a new record
|
|
341
|
+
* opens straight into another state. Requires `states`. Optional: a kind
|
|
342
|
+
* without it keeps `by` writing `reviews.decider` for every new record,
|
|
343
|
+
* as before #2756.
|
|
344
|
+
*/
|
|
345
|
+
proposedBy: z.object({ field: z.string().min(1) }).strict().optional(),
|
|
310
346
|
/**
|
|
311
347
|
* The front-matter object that says where a record came from, opted in
|
|
312
348
|
* to the stored source block (#2708): `via`, `client`, `harness`,
|
|
@@ -349,6 +385,15 @@ export const recordKindSchema = z
|
|
|
349
385
|
* date. With it, `work.ts` gives each record `ready`, `blockedBy` and
|
|
350
386
|
* `implements`, and the read lists each decision with `implementedBy`.
|
|
351
387
|
* Optional.
|
|
388
|
+
*
|
|
389
|
+
* `acceptance` (#2772) names the front-matter list of a record's
|
|
390
|
+
* acceptance criteria, each an `id`, `text` and the `verification` it
|
|
391
|
+
* expects (`unit`, `integration`, `e2e`, `runtime` or `manual`), and the
|
|
392
|
+
* field naming the record's implementer, whose own `manual` verdict never
|
|
393
|
+
* counts. With it, each record gets `acceptance` (met and total), and a
|
|
394
|
+
* done record with a criterion that no passing evidence meets is warned
|
|
395
|
+
* `work-acceptance-unmet`, which `check` fails on (WSP117). Optional, and
|
|
396
|
+
* a record without the list is read as before.
|
|
352
397
|
*/
|
|
353
398
|
work: z
|
|
354
399
|
.object({
|
|
@@ -358,6 +403,7 @@ export const recordKindSchema = z
|
|
|
358
403
|
open: z.string().min(1),
|
|
359
404
|
done: z.string().min(1),
|
|
360
405
|
closedOn: z.string().min(1),
|
|
406
|
+
acceptance: z.object({ field: z.string().min(1), implementer: z.string().min(1) }).strict().optional(),
|
|
361
407
|
})
|
|
362
408
|
.strict()
|
|
363
409
|
.optional(),
|
|
@@ -386,6 +432,10 @@ export const recordKindSchema = z
|
|
|
386
432
|
message: "a kind without states cannot have supersedes: a link takes effect only from a closed or ranked state",
|
|
387
433
|
path: ["supersedes"],
|
|
388
434
|
})
|
|
435
|
+
.refine((k) => k.states !== undefined || k.remediates === undefined, {
|
|
436
|
+
message: "a kind without states cannot have remediates: a link only ever targets a record in a closed state",
|
|
437
|
+
path: ["remediates"],
|
|
438
|
+
})
|
|
389
439
|
.refine((k) => k.states !== undefined || k.session === undefined, {
|
|
390
440
|
message: "a session kind must have states: a session is sealed when it reaches a closed state",
|
|
391
441
|
path: ["session"],
|
|
@@ -394,6 +444,10 @@ export const recordKindSchema = z
|
|
|
394
444
|
message: "a kind without states cannot have approval ranks",
|
|
395
445
|
path: ["approval"],
|
|
396
446
|
})
|
|
447
|
+
.refine((k) => k.states !== undefined || k.proposedBy === undefined, {
|
|
448
|
+
message: "a kind without states cannot have proposedBy: there is no first state for a record to open in",
|
|
449
|
+
path: ["proposedBy"],
|
|
450
|
+
})
|
|
397
451
|
.refine((k) => (k.closedStates ?? []).every((s) => (k.states ?? []).includes(s)), {
|
|
398
452
|
message: "every closed state must be listed in states",
|
|
399
453
|
path: ["closedStates"],
|
|
@@ -406,6 +460,10 @@ export const recordKindSchema = z
|
|
|
406
460
|
message: "a work kind must have states, and its work open and done states must be listed in them",
|
|
407
461
|
path: ["work"],
|
|
408
462
|
})
|
|
463
|
+
.refine((k) => !k.work?.acceptance || k.pins !== undefined, {
|
|
464
|
+
message: "a work kind with acceptance criteria must have pins: a criterion is met by the evidence in its pins field",
|
|
465
|
+
path: ["work", "acceptance"],
|
|
466
|
+
})
|
|
409
467
|
.refine(
|
|
410
468
|
(k) =>
|
|
411
469
|
!k.answers ||
|
|
@@ -961,6 +1019,13 @@ export interface RecordEntry {
|
|
|
961
1019
|
reasons: RecordReason[];
|
|
962
1020
|
/** The id of the closed record whose `supersedes` link replaces this one, or null. */
|
|
963
1021
|
supersededBy: string | null;
|
|
1022
|
+
/**
|
|
1023
|
+
* The ids of records whose `remediates` link names this one (#2774), in
|
|
1024
|
+
* path order. Unlike `supersededBy`, several records may remediate the
|
|
1025
|
+
* same one, and a remediation never changes this record's state or
|
|
1026
|
+
* `supersededBy`: it stays exactly as current as it was.
|
|
1027
|
+
*/
|
|
1028
|
+
remediatedBy: string[];
|
|
964
1029
|
/** The record's structured core as JSON (the front matter, or the whole JSON file), or null when it could not be parsed. */
|
|
965
1030
|
data: Record<string, unknown> | null;
|
|
966
1031
|
/**
|
|
@@ -981,6 +1046,8 @@ export interface RecordEntry {
|
|
|
981
1046
|
blockedBy?: WorkLink[];
|
|
982
1047
|
/** For a work kind: each decision the record implements, with its state, or null when no decision has the id. */
|
|
983
1048
|
implements?: WorkLink[];
|
|
1049
|
+
/** For a work kind with acceptance criteria (#2772): how many of the record's criteria passing evidence meets, or null when it lists none. */
|
|
1050
|
+
acceptance?: WorkAcceptance | null;
|
|
984
1051
|
}
|
|
985
1052
|
|
|
986
1053
|
export interface ReadRecordsOptions {
|
|
@@ -1100,6 +1167,20 @@ export function supersedesTargets(kind: Pick<RecordKind, "supersedes">, data: Re
|
|
|
1100
1167
|
return ids.filter((x): x is string => typeof x === "string");
|
|
1101
1168
|
}
|
|
1102
1169
|
|
|
1170
|
+
/**
|
|
1171
|
+
* The ids a record's remediates field names (#2774), read the same way
|
|
1172
|
+
* {@link supersedesTargets} reads `supersedes`.
|
|
1173
|
+
*/
|
|
1174
|
+
export function remediatesTargets(kind: Pick<RecordKind, "remediates">, data: Record<string, unknown> | null): string[] {
|
|
1175
|
+
if (!kind.remediates || data === null) return [];
|
|
1176
|
+
const { field, key } = kind.remediates;
|
|
1177
|
+
const value = data[field];
|
|
1178
|
+
if (key === undefined && typeof value === "string") return [value];
|
|
1179
|
+
if (!Array.isArray(value)) return [];
|
|
1180
|
+
const ids = key === undefined ? value : value.map((l) => (l !== null && typeof l === "object" ? (l as Record<string, unknown>)[key] : undefined));
|
|
1181
|
+
return ids.filter((x): x is string => typeof x === "string");
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1103
1184
|
/** The stem of a file name: the name up to its first `.`, the hash a content-addressed record's name claims (ws-053). */
|
|
1104
1185
|
function nameStem(path: string): string {
|
|
1105
1186
|
const name = path.slice(path.lastIndexOf("/") + 1);
|
|
@@ -1135,6 +1216,7 @@ export async function readRecords(loaded: LoadedRecordKind, options: ReadRecords
|
|
|
1135
1216
|
valid: true,
|
|
1136
1217
|
reasons: [],
|
|
1137
1218
|
supersededBy: null,
|
|
1219
|
+
remediatedBy: [],
|
|
1138
1220
|
data: null,
|
|
1139
1221
|
assets: [],
|
|
1140
1222
|
warnings: [],
|
|
@@ -1254,6 +1336,30 @@ export async function readRecords(loaded: LoadedRecordKind, options: ReadRecords
|
|
|
1254
1336
|
}
|
|
1255
1337
|
}
|
|
1256
1338
|
|
|
1339
|
+
// Remediation (#2774): a later record fixes a consequence of a closed one
|
|
1340
|
+
// without replacing it. The target's status, and its supersededBy, never
|
|
1341
|
+
// change; several records may remediate the same one. Unlike supersedes,
|
|
1342
|
+
// this needs no approval rank: the target is closed or it isn't, and a
|
|
1343
|
+
// still-open target is amended instead of remediated.
|
|
1344
|
+
for (const e of entries) {
|
|
1345
|
+
for (const target of remediatesTargets(kind, e.data)) {
|
|
1346
|
+
const old = byId.get(target);
|
|
1347
|
+
if (!old) {
|
|
1348
|
+
e.reasons.push({ code: "record-remediates-unknown", message: `remediates ${target}, which no record has` });
|
|
1349
|
+
continue;
|
|
1350
|
+
}
|
|
1351
|
+
if (old === e) continue;
|
|
1352
|
+
if (old.state === null || !closed.has(old.state)) {
|
|
1353
|
+
e.reasons.push({
|
|
1354
|
+
code: "record-remediates-not-closed",
|
|
1355
|
+
message: `remediates ${target}, which is ${old.state ?? "stateless"}, not closed; a record still open is amended instead`,
|
|
1356
|
+
});
|
|
1357
|
+
continue;
|
|
1358
|
+
}
|
|
1359
|
+
if (e.id !== null && !old.remediatedBy.includes(e.id)) old.remediatedBy.push(e.id);
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1257
1363
|
// A pin that still matches, at the hash a record this one supersedes
|
|
1258
1364
|
// pinned, while the file has not changed since this record was recorded:
|
|
1259
1365
|
// the decision moved on and the artifact did not (#2549).
|
|
@@ -311,6 +311,7 @@ describe("chant workspace status on built workspaces", () => {
|
|
|
311
311
|
{ name: "inference", broker: null, scope: [] },
|
|
312
312
|
],
|
|
313
313
|
isolation: null,
|
|
314
|
+
intent: null,
|
|
314
315
|
},
|
|
315
316
|
]);
|
|
316
317
|
});
|
|
@@ -419,7 +420,7 @@ describe("gate state in chant workspace status --json (#2674)", () => {
|
|
|
419
420
|
expiresAt: "2026-12-31T00:00:00.000Z",
|
|
420
421
|
approvals: [{ principal: "alice", channel: "cli", at: "2026-09-20T01:00:00.000Z" }],
|
|
421
422
|
needed: 1,
|
|
422
|
-
approve:
|
|
423
|
+
approve: `chant approve web deploy --env staging --plan ${D("1")}`,
|
|
423
424
|
},
|
|
424
425
|
{
|
|
425
426
|
component: "web",
|
|
@@ -431,7 +432,7 @@ describe("gate state in chant workspace status --json (#2674)", () => {
|
|
|
431
432
|
expiresAt: "2026-12-31T00:00:00.000Z",
|
|
432
433
|
approvals: [],
|
|
433
434
|
needed: 1,
|
|
434
|
-
approve:
|
|
435
|
+
approve: `chant approve web deploy --env prod --plan ${D("2")}`,
|
|
435
436
|
},
|
|
436
437
|
{
|
|
437
438
|
component: "web",
|
|
@@ -444,15 +445,15 @@ describe("gate state in chant workspace status --json (#2674)", () => {
|
|
|
444
445
|
// The agent's approval doesn't count toward the quorum.
|
|
445
446
|
approvals: [{ principal: "bob", channel: "cli", at: "2026-09-20T02:00:00.000Z" }],
|
|
446
447
|
needed: 2,
|
|
447
|
-
approve:
|
|
448
|
+
approve: `chant approve web review --env staging --plan ${D("4")}`,
|
|
448
449
|
},
|
|
449
450
|
]);
|
|
450
451
|
// api and the root member both read the flat _gates/.
|
|
451
452
|
expect(api.gateLedger).toEqual({ layout: "flat", path: "_gates", shared: true, malformed: 1, reason: null });
|
|
452
453
|
expect(site.gates).toEqual(api.gates);
|
|
453
454
|
expect(api.gates.map((g) => [g.component, g.name, g.env, g.state, g.approve])).toEqual([
|
|
454
|
-
["api", "deploy", "staging", "expired",
|
|
455
|
-
["api", "smoke", "staging", "superseded",
|
|
455
|
+
["api", "deploy", "staging", "expired", `chant approve api deploy --env staging --plan ${D("5")}`],
|
|
456
|
+
["api", "smoke", "staging", "superseded", `chant approve api smoke --env staging --plan ${D("6")}`],
|
|
456
457
|
["nightly", "confirm", null, "approved", "chant approve nightly confirm"],
|
|
457
458
|
]);
|
|
458
459
|
// The superseding approval named another plan, so it isn't listed as one that counts.
|
|
@@ -514,7 +515,7 @@ describe("gate state in chant workspace status --json (#2674)", () => {
|
|
|
514
515
|
expect(await runWorkspaceStatus({ args: { extraPositional: "staging", extraPositional2: root, json: true } } as unknown as CommandContext)).toBe(0);
|
|
515
516
|
const printed = JSON.parse(String(log.mock.calls.at(-1)?.[0])) as StatusDocument;
|
|
516
517
|
expectValid(printed);
|
|
517
|
-
expect(result(printed).members.find((m) => m.name === "web")!.gates.map((g) => g.approve)).toContain(
|
|
518
|
+
expect(result(printed).members.find((m) => m.name === "web")!.gates.map((g) => g.approve)).toContain(`chant approve web deploy --env staging --plan ${D("1")}`);
|
|
518
519
|
} finally {
|
|
519
520
|
log.mockRestore();
|
|
520
521
|
}
|
|
@@ -594,16 +595,19 @@ describe("stewards in chant workspace status --json (#2731)", () => {
|
|
|
594
595
|
name: "box-converge",
|
|
595
596
|
schedule: { cron: "* * * * *", overlap: "skip" },
|
|
596
597
|
env: "local",
|
|
597
|
-
lastRun: { id: expect.any(String), status: "ok", started: "2026-09-25T10:00:00.000Z", ended: "2026-09-25T10:00:05.000Z", gate: null },
|
|
598
|
+
lastRun: { id: expect.any(String), status: "ok", started: "2026-09-25T10:00:00.000Z", ended: "2026-09-25T10:00:05.000Z", gate: null, point: null },
|
|
599
|
+
// #2778: not a ConvergeOp (no Converge label), so no tick.
|
|
600
|
+
lastTick: null,
|
|
598
601
|
changesCheckout: false,
|
|
599
602
|
workLease: null,
|
|
600
603
|
},
|
|
601
|
-
{ name: "box-release", schedule: null, env: "local", lastRun: null, changesCheckout: false, workLease: null },
|
|
604
|
+
{ name: "box-release", schedule: null, env: "local", lastRun: null, lastTick: null, changesCheckout: false, workLease: null },
|
|
602
605
|
{
|
|
603
606
|
name: "box-dispatch",
|
|
604
607
|
schedule: { cron: "*/5 * * * *", overlap: "skip" },
|
|
605
608
|
env: "local",
|
|
606
609
|
lastRun: null,
|
|
610
|
+
lastTick: null,
|
|
607
611
|
changesCheckout: true,
|
|
608
612
|
workLease: {
|
|
609
613
|
kind: null,
|
|
@@ -620,6 +624,8 @@ describe("stewards in chant workspace status --json (#2731)", () => {
|
|
|
620
624
|
},
|
|
621
625
|
},
|
|
622
626
|
]);
|
|
627
|
+
// #2749: nothing the steward runs waits on a decision point.
|
|
628
|
+
expect(s.waiting).toEqual([]);
|
|
623
629
|
|
|
624
630
|
const k3d = result(await workspaceStatus({ cwd: root, env: "fountain-k3d", now: "2026-09-25T11:00:00.000Z" }));
|
|
625
631
|
expectValid(k3d);
|
|
@@ -103,9 +103,14 @@ export const defaultGateLedgerReader: GateLedgerReader = async (dir, commit, cwd
|
|
|
103
103
|
return files;
|
|
104
104
|
};
|
|
105
105
|
|
|
106
|
-
/**
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
/**
|
|
107
|
+
* The exact `chant approve` line for a gate. A gate bound to a plan (#2300)
|
|
108
|
+
* gets `--plan <digest>` too (#2832), so running the line back approves the
|
|
109
|
+
* plan this read showed and not whatever happens to be pending by the time
|
|
110
|
+
* someone runs it.
|
|
111
|
+
*/
|
|
112
|
+
export function approveCommand(component: string, gate: string, env: string | null, planDigest?: string | null): string {
|
|
113
|
+
return `chant approve ${component} ${gate}${env !== null ? ` --env ${env}` : ""}${planDigest ? ` --plan ${planDigest}` : ""}`;
|
|
109
114
|
}
|
|
110
115
|
|
|
111
116
|
const approvalOf = (r: GateResolutionRecord): StatusGateApproval => ({ principal: r.resolvedBy, channel: r.origin ?? null, at: r.timestamp });
|
|
@@ -150,7 +155,7 @@ function decide(component: string, standing: PendingGateRecord, resolutions: Gat
|
|
|
150
155
|
expiresAt: standing.expiresAt,
|
|
151
156
|
approvals: approvals.map(approvalOf),
|
|
152
157
|
needed,
|
|
153
|
-
approve: approveCommand(component, gate, env),
|
|
158
|
+
approve: approveCommand(component, gate, env, planDigest),
|
|
154
159
|
};
|
|
155
160
|
}
|
|
156
161
|
|
|
@@ -14,6 +14,13 @@
|
|
|
14
14
|
* - each Op's last run is the newest record in its run ledger,
|
|
15
15
|
* `<env>/runs__<op>.jsonl` under the member's ledger prefix, where `<env>`
|
|
16
16
|
* is the Op's own `labels.Env` or `local`;
|
|
17
|
+
* - `waiting` lists the open decision points the steward waits on (#2749):
|
|
18
|
+
* each Op whose newest run is the steward's own and stopped on a question,
|
|
19
|
+
* as the run ledger records it. The question's state now is `points`'s.
|
|
20
|
+
* A run is the steward's when its record names it (`steward`, written for
|
|
21
|
+
* a run started in the steward's turn or under `CHANT_STEWARD`), so this
|
|
22
|
+
* also covers the member's other Ops, those no steward lists, that such a
|
|
23
|
+
* run belongs to, such as an Op the steward's process starts itself;
|
|
17
24
|
* - `lease` is the local steward's own lease ref
|
|
18
25
|
* (`refs/chant/lease/[<prefix>]_stewards/<name>`), present while a
|
|
19
26
|
* `chant operator --steward` holds it or until it expires;
|
|
@@ -24,9 +31,10 @@
|
|
|
24
31
|
|
|
25
32
|
import { existsSync, realpathSync } from "node:fs";
|
|
26
33
|
import { dirname, join, relative, resolve } from "node:path";
|
|
27
|
-
import { discoverStewards } from "../op/discover";
|
|
34
|
+
import { discoverOps, discoverStewards } from "../op/discover";
|
|
28
35
|
import { stewardFormFor, stewardLeaseName, type StewardForm } from "../op/steward";
|
|
29
36
|
import { readRunLedger, runEnvOf } from "../lifecycle/run-ledger";
|
|
37
|
+
import { readConvergeLedger, type ConvergeTickRecord } from "../lifecycle/converge-ledger";
|
|
30
38
|
import { leaseRef } from "../lifecycle/lease";
|
|
31
39
|
import { readBlobBySha, readRefSha } from "../lifecycle/git";
|
|
32
40
|
import { resolveMemberLedger } from "../lifecycle/member-ledger";
|
|
@@ -34,6 +42,7 @@ import { listWorkLeases } from "../lifecycle/work-lease";
|
|
|
34
42
|
import { stewardWorkHolder } from "../op/work-lease-run";
|
|
35
43
|
import type { OpConfig } from "../op/types";
|
|
36
44
|
import type { OpRunRecord } from "../op/runtime";
|
|
45
|
+
import { approveCommand } from "./status-gates";
|
|
37
46
|
import type { ReasonCode } from "./reason-codes";
|
|
38
47
|
|
|
39
48
|
/** Why a member's stewards can't be fully listed. Closed: a new code is a contract change. */
|
|
@@ -42,7 +51,7 @@ export const STEWARD_REASON_CODES = [
|
|
|
42
51
|
"stewards-unreadable",
|
|
43
52
|
/** A steward was dropped: its name, or an Op it lists, belongs to another steward. */
|
|
44
53
|
"stewards-conflict",
|
|
45
|
-
/** Reading an Op's run ledger failed, so its last run is null. */
|
|
54
|
+
/** Reading an Op's run ledger, or a ConvergeOp's converge ledger, failed, so its last run or last tick is null. */
|
|
46
55
|
"steward-runs-unreadable",
|
|
47
56
|
] as const satisfies readonly ReasonCode[];
|
|
48
57
|
export type StewardReasonCode = (typeof STEWARD_REASON_CODES)[number];
|
|
@@ -52,8 +61,48 @@ export interface StatusStewardRun {
|
|
|
52
61
|
status: OpRunRecord["status"];
|
|
53
62
|
started: string;
|
|
54
63
|
ended: string;
|
|
55
|
-
/**
|
|
56
|
-
|
|
64
|
+
/**
|
|
65
|
+
* The gate the run stopped at, for a `gated` run: its name, when the run
|
|
66
|
+
* stopped there, the op it is recorded under (the Op's own, or the
|
|
67
|
+
* command's for a step whose command stopped at its own gate, #2779, such
|
|
68
|
+
* as `workspace-upgrade`), and the command that approves it.
|
|
69
|
+
*/
|
|
70
|
+
gate: { name: string; since: string; op: string; approve: string } | null;
|
|
71
|
+
/** The open decision point the run stopped on, for a `waiting` run (#2749). */
|
|
72
|
+
point: StatusStewardWait | null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** An open decision point a steward's run stopped on (#2749), as the run ledger records it. */
|
|
76
|
+
export interface StatusStewardWait {
|
|
77
|
+
/** The answer record's id: what `points --open` lists and `points answer` names. */
|
|
78
|
+
id: string;
|
|
79
|
+
point: string;
|
|
80
|
+
/** The question's state when the run stopped. */
|
|
81
|
+
state: "escalated" | "proposed";
|
|
82
|
+
path: string;
|
|
83
|
+
subject: string | null;
|
|
84
|
+
since: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** A ConvergeOp's newest tick on the converge ledger (#2778). */
|
|
88
|
+
export interface StatusStewardTick {
|
|
89
|
+
/** The tick's id, or null for a tick recorded before ticks had ids. */
|
|
90
|
+
id: string | null;
|
|
91
|
+
timestamp: string;
|
|
92
|
+
/** The tick's one log line. */
|
|
93
|
+
log: string;
|
|
94
|
+
/** Every rule that fired. */
|
|
95
|
+
firedRuleIds: string[];
|
|
96
|
+
/** What each fired rule did, and for which resource on a ConvergeOp with an observer step. */
|
|
97
|
+
outcomes: {
|
|
98
|
+
ruleId: string;
|
|
99
|
+
action: ConvergeTickRecord["outcomes"][number]["action"];
|
|
100
|
+
op: string | null;
|
|
101
|
+
resource: string | null;
|
|
102
|
+
reason: string | null;
|
|
103
|
+
}[];
|
|
104
|
+
/** What the observer step reported, or null for a ConvergeOp that observes a lexicon environment. */
|
|
105
|
+
resources: { name: string; status: "in-sync" | "drifted" | "unknown"; detail: string | null }[] | null;
|
|
57
106
|
}
|
|
58
107
|
|
|
59
108
|
export interface StatusStewardOp {
|
|
@@ -64,6 +113,8 @@ export interface StatusStewardOp {
|
|
|
64
113
|
env: string;
|
|
65
114
|
/** The newest run in its run ledger, or null when it has none. */
|
|
66
115
|
lastRun: StatusStewardRun | null;
|
|
116
|
+
/** For a ConvergeOp, its newest tick on the converge ledger (#2778); null for any other Op, or before its first tick. */
|
|
117
|
+
lastTick: StatusStewardTick | null;
|
|
67
118
|
/** Whether the Op changes the checkout, and so runs under a work lease on a branch of its own (#2748). */
|
|
68
119
|
changesCheckout: boolean;
|
|
69
120
|
/**
|
|
@@ -103,6 +154,13 @@ export interface StatusSteward {
|
|
|
103
154
|
/** The local steward's own lease, or null when no local operator has held it. */
|
|
104
155
|
lease: { holder: string; acquiredAt: string; expiresAt: string; live: boolean } | null;
|
|
105
156
|
ops: StatusStewardOp[];
|
|
157
|
+
/**
|
|
158
|
+
* The open decision points the steward waits on (#2749): each Op whose
|
|
159
|
+
* newest run is the steward's own and stopped on a question. Its declared
|
|
160
|
+
* Ops come first, in `ops` order, then any other Op of the member whose
|
|
161
|
+
* newest run names the steward, by Op name.
|
|
162
|
+
*/
|
|
163
|
+
waiting: (StatusStewardWait & { op: string; run: string })[];
|
|
106
164
|
}
|
|
107
165
|
|
|
108
166
|
export interface MemberStewards {
|
|
@@ -110,6 +168,39 @@ export interface MemberStewards {
|
|
|
110
168
|
reasons: { code: StewardReasonCode; message: string }[];
|
|
111
169
|
}
|
|
112
170
|
|
|
171
|
+
function waitOf(p: NonNullable<OpRunRecord["point"]>): StatusStewardWait {
|
|
172
|
+
return {
|
|
173
|
+
id: p.id,
|
|
174
|
+
point: p.point,
|
|
175
|
+
state: p.state === "proposed" ? "proposed" : "escalated",
|
|
176
|
+
path: p.path,
|
|
177
|
+
subject: p.subject ?? null,
|
|
178
|
+
since: p.since,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function gateOf(g: NonNullable<OpRunRecord["gate"]>, opName: string): NonNullable<StatusStewardRun["gate"]> {
|
|
183
|
+
const op = g.op ?? opName;
|
|
184
|
+
return { name: g.name, since: g.since, op, approve: approveCommand(op, g.name, null) };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function tickOf(t: ConvergeTickRecord): StatusStewardTick {
|
|
188
|
+
return {
|
|
189
|
+
id: t.id ?? null,
|
|
190
|
+
timestamp: t.timestamp,
|
|
191
|
+
log: t.log,
|
|
192
|
+
firedRuleIds: [...t.firedRuleIds],
|
|
193
|
+
outcomes: t.outcomes.map((o) => ({
|
|
194
|
+
ruleId: o.ruleId,
|
|
195
|
+
action: o.action,
|
|
196
|
+
op: o.op ?? null,
|
|
197
|
+
resource: o.resource ?? null,
|
|
198
|
+
reason: o.reason ?? null,
|
|
199
|
+
})),
|
|
200
|
+
resources: t.resources ? t.resources.map((r) => ({ name: r.name, status: r.status, detail: r.detail ?? null })) : null,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
113
204
|
/** Whether a directory is a chant project of its own. */
|
|
114
205
|
function isChantProject(dir: string): boolean {
|
|
115
206
|
return existsSync(join(dir, "chant.config.ts")) || existsSync(join(dir, "chant.config.json"));
|
|
@@ -176,6 +267,7 @@ export async function readMemberStewards(
|
|
|
176
267
|
const stewards: StatusSteward[] = [];
|
|
177
268
|
for (const { declaration, filePath } of [...found.values()].sort((a, b) => a.declaration.name.localeCompare(b.declaration.name))) {
|
|
178
269
|
const ops: StatusStewardOp[] = [];
|
|
270
|
+
const waiting: StatusSteward["waiting"] = [];
|
|
179
271
|
for (const op of declaration.ops) {
|
|
180
272
|
const opEnv = runEnvOf(op);
|
|
181
273
|
let lastRun: StatusStewardRun | null = null;
|
|
@@ -187,8 +279,12 @@ export async function readMemberStewards(
|
|
|
187
279
|
status: newest.status,
|
|
188
280
|
started: newest.started,
|
|
189
281
|
ended: newest.ended,
|
|
190
|
-
gate: newest.gate ?
|
|
282
|
+
gate: newest.gate ? gateOf(newest.gate, op.name) : null,
|
|
283
|
+
point: newest.point ? waitOf(newest.point) : null,
|
|
191
284
|
};
|
|
285
|
+
if (newest.status === "waiting" && lastRun.point && newest.steward === declaration.name) {
|
|
286
|
+
waiting.push({ op: op.name, run: newest.id, ...lastRun.point });
|
|
287
|
+
}
|
|
192
288
|
}
|
|
193
289
|
} catch (err) {
|
|
194
290
|
reasons.push({
|
|
@@ -196,11 +292,24 @@ export async function readMemberStewards(
|
|
|
196
292
|
message: `${opEnv}/runs__${op.name}.jsonl: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`,
|
|
197
293
|
});
|
|
198
294
|
}
|
|
295
|
+
let lastTick: StatusStewardTick | null = null;
|
|
296
|
+
if (op.labels?.Converge === "true") {
|
|
297
|
+
try {
|
|
298
|
+
const newest = (await readConvergeLedger(opEnv, { cwd: memberDir })).records.filter((r) => r.op === op.name).at(-1);
|
|
299
|
+
if (newest) lastTick = tickOf(newest);
|
|
300
|
+
} catch (err) {
|
|
301
|
+
reasons.push({
|
|
302
|
+
code: "steward-runs-unreadable",
|
|
303
|
+
message: `${opEnv}/converge.jsonl: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`,
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
}
|
|
199
307
|
ops.push({
|
|
200
308
|
name: op.name,
|
|
201
309
|
schedule: op.schedule ? { cron: op.schedule.cron, overlap: "skip" } : null,
|
|
202
310
|
env: opEnv,
|
|
203
311
|
lastRun,
|
|
312
|
+
lastTick,
|
|
204
313
|
changesCheckout: op.changesCheckout === true,
|
|
205
314
|
workLease: op.workLease
|
|
206
315
|
? { kind: op.workLease.kind ?? null, held: await readHeldWorkLeases(declaration.name, op, memberDir, now) }
|
|
@@ -219,7 +328,48 @@ export async function readMemberStewards(
|
|
|
219
328
|
}),
|
|
220
329
|
lease: await readStewardLease(declaration.name, memberDir, now),
|
|
221
330
|
ops,
|
|
331
|
+
waiting,
|
|
222
332
|
});
|
|
223
333
|
}
|
|
334
|
+
if (stewards.length > 0) await addUndeclaredWaits(memberDir, stewards, found, reasons);
|
|
224
335
|
return { stewards, reasons };
|
|
225
336
|
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* The waiting runs of the member's Ops that no steward lists, each under the
|
|
340
|
+
* steward its record names. A steward's process can start an Op itself
|
|
341
|
+
* with `CHANT_STEWARD` set (studio#137), and the run ledger then records
|
|
342
|
+
* the run as the steward's though the declaration does not list the Op.
|
|
343
|
+
* Import failures are left out here: `discoverStewards` read the same files
|
|
344
|
+
* and reported them as `stewards-unreadable`.
|
|
345
|
+
*/
|
|
346
|
+
async function addUndeclaredWaits(
|
|
347
|
+
memberDir: string,
|
|
348
|
+
stewards: StatusSteward[],
|
|
349
|
+
found: Awaited<ReturnType<typeof discoverStewards>>["stewards"],
|
|
350
|
+
reasons: MemberStewards["reasons"],
|
|
351
|
+
): Promise<void> {
|
|
352
|
+
const declared = new Set([...found.values()].flatMap(({ declaration }) => declaration.ops.map((op) => op.name)));
|
|
353
|
+
let ops: OpConfig[];
|
|
354
|
+
try {
|
|
355
|
+
ops = [...(await discoverOps({ cwd: memberDir })).ops.values()].map((d) => d.config).filter((op) => !declared.has(op.name));
|
|
356
|
+
} catch {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
const byName = new Map(stewards.map((s) => [s.name, s]));
|
|
360
|
+
for (const op of ops.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
361
|
+
const opEnv = runEnvOf(op);
|
|
362
|
+
try {
|
|
363
|
+
const newest = (await readRunLedger(opEnv, op.name, { cwd: memberDir })).records.at(-1);
|
|
364
|
+
const steward = newest?.steward ? byName.get(newest.steward) : undefined;
|
|
365
|
+
if (steward && newest?.status === "waiting" && newest.point) {
|
|
366
|
+
steward.waiting.push({ op: op.name, run: newest.id, ...waitOf(newest.point) });
|
|
367
|
+
}
|
|
368
|
+
} catch (err) {
|
|
369
|
+
reasons.push({
|
|
370
|
+
code: "steward-runs-unreadable",
|
|
371
|
+
message: `${opEnv}/runs__${op.name}.jsonl: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`,
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|