wingfoil 0.2.1 → 0.3.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/README.md +88 -42
- package/dist/agent/discovery.d.ts +72 -0
- package/dist/agent/discovery.d.ts.map +1 -0
- package/dist/agent/discovery.js +210 -0
- package/dist/agent/discovery.js.map +1 -0
- package/dist/agent/execute.d.ts +206 -0
- package/dist/agent/execute.d.ts.map +1 -0
- package/dist/agent/execute.js +581 -0
- package/dist/agent/execute.js.map +1 -0
- package/dist/agent/index.d.ts +23 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +85 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent/launch.d.ts +133 -0
- package/dist/agent/launch.d.ts.map +1 -0
- package/dist/agent/launch.js +380 -0
- package/dist/agent/launch.js.map +1 -0
- package/dist/agent/manifest.d.ts +30 -0
- package/dist/agent/manifest.d.ts.map +1 -0
- package/dist/agent/manifest.js +93 -0
- package/dist/agent/manifest.js.map +1 -0
- package/dist/agent/placeholders.d.ts +49 -0
- package/dist/agent/placeholders.d.ts.map +1 -0
- package/dist/agent/placeholders.js +177 -0
- package/dist/agent/placeholders.js.map +1 -0
- package/dist/agent/run-log.d.ts +254 -0
- package/dist/agent/run-log.d.ts.map +1 -0
- package/dist/agent/run-log.js +612 -0
- package/dist/agent/run-log.js.map +1 -0
- package/dist/agent/schema.d.ts +100 -0
- package/dist/agent/schema.d.ts.map +1 -0
- package/dist/agent/schema.js +89 -0
- package/dist/agent/schema.js.map +1 -0
- package/dist/agent/step.d.ts +41 -0
- package/dist/agent/step.d.ts.map +1 -0
- package/dist/agent/step.js +95 -0
- package/dist/agent/step.js.map +1 -0
- package/dist/build-info.json +4 -0
- package/dist/cli/error.d.ts +11 -2
- package/dist/cli/error.d.ts.map +1 -1
- package/dist/cli/error.js +27 -6
- package/dist/cli/error.js.map +1 -1
- package/dist/cli/exit.d.ts +33 -3
- package/dist/cli/exit.d.ts.map +1 -1
- package/dist/cli/exit.js +54 -3
- package/dist/cli/exit.js.map +1 -1
- package/dist/cli/index.d.ts +2 -2
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +3 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init-command.d.ts.map +1 -1
- package/dist/cli/init-command.js +6 -3
- package/dist/cli/init-command.js.map +1 -1
- package/dist/cli/mcp-command.d.ts +13 -4
- package/dist/cli/mcp-command.d.ts.map +1 -1
- package/dist/cli/mcp-command.js +25 -8
- package/dist/cli/mcp-command.js.map +1 -1
- package/dist/cli/output.d.ts +12 -4
- package/dist/cli/output.d.ts.map +1 -1
- package/dist/cli/output.js +15 -4
- package/dist/cli/output.js.map +1 -1
- package/dist/cli/program.d.ts +1 -1
- package/dist/cli/program.d.ts.map +1 -1
- package/dist/cli/program.js +270 -60
- package/dist/cli/program.js.map +1 -1
- package/dist/cli/registrar.d.ts +16 -7
- package/dist/cli/registrar.d.ts.map +1 -1
- package/dist/cli/registrar.js +71 -6
- package/dist/cli/registrar.js.map +1 -1
- package/dist/cli/suggest.d.ts +20 -0
- package/dist/cli/suggest.d.ts.map +1 -0
- package/dist/cli/suggest.js +49 -0
- package/dist/cli/suggest.js.map +1 -0
- package/dist/cli/warning.d.ts +26 -0
- package/dist/cli/warning.d.ts.map +1 -0
- package/dist/cli/warning.js +66 -0
- package/dist/cli/warning.js.map +1 -0
- package/dist/cli.js +11 -2
- package/dist/cli.js.map +1 -1
- package/dist/core/agent-execute.d.ts +44 -0
- package/dist/core/agent-execute.d.ts.map +1 -0
- package/dist/core/agent-execute.js +71 -0
- package/dist/core/agent-execute.js.map +1 -0
- package/dist/core/agent-list.d.ts +95 -0
- package/dist/core/agent-list.d.ts.map +1 -0
- package/dist/core/agent-list.js +207 -0
- package/dist/core/agent-list.js.map +1 -0
- package/dist/core/agent-show.d.ts +44 -0
- package/dist/core/agent-show.d.ts.map +1 -0
- package/dist/core/agent-show.js +188 -0
- package/dist/core/agent-show.js.map +1 -0
- package/dist/core/approval-authority.d.ts +17 -14
- package/dist/core/approval-authority.d.ts.map +1 -1
- package/dist/core/approval-authority.js +9 -9
- package/dist/core/approval-authority.js.map +1 -1
- package/dist/core/builtin-asset.d.ts +18 -15
- package/dist/core/builtin-asset.d.ts.map +1 -1
- package/dist/core/builtin-asset.js +17 -14
- package/dist/core/builtin-asset.js.map +1 -1
- package/dist/core/builtin-integrity.d.ts +12 -2
- package/dist/core/builtin-integrity.d.ts.map +1 -1
- package/dist/core/builtin-integrity.js +85 -9
- package/dist/core/builtin-integrity.js.map +1 -1
- package/dist/core/confinement.d.ts +3 -1
- package/dist/core/confinement.d.ts.map +1 -1
- package/dist/core/confinement.js +7 -4
- package/dist/core/confinement.js.map +1 -1
- package/dist/core/context.d.ts +150 -98
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/context.js +602 -66
- package/dist/core/context.js.map +1 -1
- package/dist/core/directive-assign.d.ts +33 -7
- package/dist/core/directive-assign.d.ts.map +1 -1
- package/dist/core/directive-assign.js +67 -27
- package/dist/core/directive-assign.js.map +1 -1
- package/dist/core/directive-scope.d.ts +36 -0
- package/dist/core/directive-scope.d.ts.map +1 -0
- package/dist/core/directive-scope.js +34 -0
- package/dist/core/directive-scope.js.map +1 -0
- package/dist/core/directives-list.d.ts +13 -3
- package/dist/core/directives-list.d.ts.map +1 -1
- package/dist/core/directives-list.js +18 -6
- package/dist/core/directives-list.js.map +1 -1
- package/dist/core/dry-run.d.ts +33 -0
- package/dist/core/dry-run.d.ts.map +1 -0
- package/dist/core/dry-run.js +45 -0
- package/dist/core/dry-run.js.map +1 -0
- package/dist/core/element-ref.d.ts +23 -0
- package/dist/core/element-ref.d.ts.map +1 -0
- package/dist/core/element-ref.js +24 -0
- package/dist/core/element-ref.js.map +1 -0
- package/dist/core/error-details.d.ts +20 -0
- package/dist/core/error-details.d.ts.map +1 -0
- package/dist/core/error-details.js +65 -0
- package/dist/core/error-details.js.map +1 -0
- package/dist/core/exit-code.js +6 -6
- package/dist/core/git-identity.d.ts +46 -11
- package/dist/core/git-identity.d.ts.map +1 -1
- package/dist/core/git-identity.js +140 -27
- package/dist/core/git-identity.js.map +1 -1
- package/dist/core/index.d.ts +189 -14
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1241 -238
- package/dist/core/index.js.map +1 -1
- package/dist/core/init.d.ts +50 -8
- package/dist/core/init.d.ts.map +1 -1
- package/dist/core/init.js +139 -27
- package/dist/core/init.js.map +1 -1
- package/dist/core/loaders.d.ts +170 -14
- package/dist/core/loaders.d.ts.map +1 -1
- package/dist/core/loaders.js +570 -89
- package/dist/core/loaders.js.map +1 -1
- package/dist/core/memory-add-link.d.ts +28 -0
- package/dist/core/memory-add-link.d.ts.map +1 -0
- package/dist/core/memory-add-link.js +67 -0
- package/dist/core/memory-add-link.js.map +1 -0
- package/dist/core/memory-add-type.d.ts +25 -1
- package/dist/core/memory-add-type.d.ts.map +1 -1
- package/dist/core/memory-add-type.js +59 -1
- package/dist/core/memory-add-type.js.map +1 -1
- package/dist/core/memory-amend.d.ts +80 -0
- package/dist/core/memory-amend.d.ts.map +1 -0
- package/dist/core/memory-amend.js +160 -0
- package/dist/core/memory-amend.js.map +1 -0
- package/dist/core/memory-supersede.d.ts +28 -0
- package/dist/core/memory-supersede.d.ts.map +1 -0
- package/dist/core/memory-supersede.js +79 -0
- package/dist/core/memory-supersede.js.map +1 -0
- package/dist/core/memory-transition.d.ts +190 -30
- package/dist/core/memory-transition.d.ts.map +1 -1
- package/dist/core/memory-transition.js +480 -59
- package/dist/core/memory-transition.js.map +1 -1
- package/dist/core/registry.d.ts +147 -23
- package/dist/core/registry.d.ts.map +1 -1
- package/dist/core/registry.js +50 -0
- package/dist/core/registry.js.map +1 -1
- package/dist/core/relevance.d.ts +74 -12
- package/dist/core/relevance.d.ts.map +1 -1
- package/dist/core/relevance.js +45 -27
- package/dist/core/relevance.js.map +1 -1
- package/dist/core/require-reason.d.ts.map +1 -1
- package/dist/core/require-reason.js +6 -5
- package/dist/core/require-reason.js.map +1 -1
- package/dist/core/required-fields.d.ts +28 -0
- package/dist/core/required-fields.d.ts.map +1 -0
- package/dist/core/required-fields.js +60 -0
- package/dist/core/required-fields.js.map +1 -0
- package/dist/core/revision.d.ts +82 -0
- package/dist/core/revision.d.ts.map +1 -0
- package/dist/core/revision.js +144 -0
- package/dist/core/revision.js.map +1 -0
- package/dist/core/types.d.ts +28 -2
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +12 -3
- package/dist/core/types.js.map +1 -1
- package/dist/core/workflow-core-checks.d.ts +34 -0
- package/dist/core/workflow-core-checks.d.ts.map +1 -0
- package/dist/core/workflow-core-checks.js +403 -0
- package/dist/core/workflow-core-checks.js.map +1 -0
- package/dist/core/workflow-create-remove.d.ts +65 -0
- package/dist/core/workflow-create-remove.d.ts.map +1 -0
- package/dist/core/workflow-create-remove.js +277 -0
- package/dist/core/workflow-create-remove.js.map +1 -0
- package/dist/core/workflow-deduction.d.ts +48 -0
- package/dist/core/workflow-deduction.d.ts.map +1 -0
- package/dist/core/workflow-deduction.js +523 -0
- package/dist/core/workflow-deduction.js.map +1 -0
- package/dist/core/workflow-diagnostics.d.ts +83 -0
- package/dist/core/workflow-diagnostics.d.ts.map +1 -0
- package/dist/core/workflow-diagnostics.js +375 -0
- package/dist/core/workflow-diagnostics.js.map +1 -0
- package/dist/core/workflow-exit-state.d.ts +125 -0
- package/dist/core/workflow-exit-state.d.ts.map +1 -0
- package/dist/core/workflow-exit-state.js +243 -0
- package/dist/core/workflow-exit-state.js.map +1 -0
- package/dist/core/workflow-finalize.d.ts +37 -0
- package/dist/core/workflow-finalize.d.ts.map +1 -0
- package/dist/core/workflow-finalize.js +153 -0
- package/dist/core/workflow-finalize.js.map +1 -0
- package/dist/core/workflow-list-show.d.ts +176 -0
- package/dist/core/workflow-list-show.d.ts.map +1 -0
- package/dist/core/workflow-list-show.js +268 -0
- package/dist/core/workflow-list-show.js.map +1 -0
- package/dist/core/workflow-next.d.ts +268 -0
- package/dist/core/workflow-next.d.ts.map +1 -0
- package/dist/core/workflow-next.js +503 -0
- package/dist/core/workflow-next.js.map +1 -0
- package/dist/core/workflow-registry.d.ts +52 -0
- package/dist/core/workflow-registry.d.ts.map +1 -0
- package/dist/core/workflow-registry.js +158 -0
- package/dist/core/workflow-registry.js.map +1 -0
- package/dist/core/workflow-start-end.d.ts +71 -0
- package/dist/core/workflow-start-end.d.ts.map +1 -0
- package/dist/core/workflow-start-end.js +298 -0
- package/dist/core/workflow-start-end.js.map +1 -0
- package/dist/core/workflow-status.d.ts +83 -0
- package/dist/core/workflow-status.d.ts.map +1 -0
- package/dist/core/workflow-status.js +125 -0
- package/dist/core/workflow-status.js.map +1 -0
- package/dist/core/write-guard.d.ts +51 -8
- package/dist/core/write-guard.d.ts.map +1 -1
- package/dist/core/write-guard.js +139 -9
- package/dist/core/write-guard.js.map +1 -1
- package/dist/directives/index.d.ts +3 -2
- package/dist/directives/index.d.ts.map +1 -1
- package/dist/directives/index.js +5 -3
- package/dist/directives/index.js.map +1 -1
- package/dist/directives/schema.d.ts +35 -30
- package/dist/directives/schema.d.ts.map +1 -1
- package/dist/directives/schema.js +36 -30
- package/dist/directives/schema.js.map +1 -1
- package/dist/directives/version.d.ts +28 -0
- package/dist/directives/version.d.ts.map +1 -0
- package/dist/directives/version.js +51 -0
- package/dist/directives/version.js.map +1 -0
- package/dist/dna/edit.d.ts +7 -6
- package/dist/dna/edit.d.ts.map +1 -1
- package/dist/dna/edit.js +96 -41
- package/dist/dna/edit.js.map +1 -1
- package/dist/dna/mutate.js +18 -3
- package/dist/dna/mutate.js.map +1 -1
- package/dist/dna/path.d.ts +2 -2
- package/dist/dna/roles.d.ts +1 -1
- package/dist/dna/schema.d.ts +61 -3
- package/dist/dna/schema.d.ts.map +1 -1
- package/dist/dna/schema.js +121 -6
- package/dist/dna/schema.js.map +1 -1
- package/dist/mcp/dna-resource.d.ts.map +1 -1
- package/dist/mcp/dna-resource.js +4 -4
- package/dist/mcp/dna-resource.js.map +1 -1
- package/dist/mcp/index.d.ts +9 -5
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +13 -2
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/memory-resource.d.ts.map +1 -1
- package/dist/mcp/memory-resource.js +30 -19
- package/dist/mcp/memory-resource.js.map +1 -1
- package/dist/mcp/prompt.d.ts +61 -24
- package/dist/mcp/prompt.d.ts.map +1 -1
- package/dist/mcp/prompt.js +176 -47
- package/dist/mcp/prompt.js.map +1 -1
- package/dist/mcp/read-only.d.ts +39 -0
- package/dist/mcp/read-only.d.ts.map +1 -1
- package/dist/mcp/read-only.js +61 -0
- package/dist/mcp/read-only.js.map +1 -1
- package/dist/mcp/registrar.d.ts +10 -0
- package/dist/mcp/registrar.d.ts.map +1 -1
- package/dist/mcp/registrar.js +48 -12
- package/dist/mcp/registrar.js.map +1 -1
- package/dist/mcp/server.d.ts +21 -14
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +41 -15
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/session-resource.d.ts +13 -0
- package/dist/mcp/session-resource.d.ts.map +1 -0
- package/dist/mcp/session-resource.js +65 -0
- package/dist/mcp/session-resource.js.map +1 -0
- package/dist/mcp/workflow-resource.d.ts +18 -0
- package/dist/mcp/workflow-resource.d.ts.map +1 -1
- package/dist/mcp/workflow-resource.js +37 -8
- package/dist/mcp/workflow-resource.js.map +1 -1
- package/dist/memory/add.d.ts +160 -17
- package/dist/memory/add.d.ts.map +1 -1
- package/dist/memory/add.js +361 -51
- package/dist/memory/add.js.map +1 -1
- package/dist/memory/audit.d.ts +165 -32
- package/dist/memory/audit.d.ts.map +1 -1
- package/dist/memory/audit.js +267 -97
- package/dist/memory/audit.js.map +1 -1
- package/dist/memory/commit-message.d.ts +60 -38
- package/dist/memory/commit-message.d.ts.map +1 -1
- package/dist/memory/commit-message.js +167 -21
- package/dist/memory/commit-message.js.map +1 -1
- package/dist/memory/entry.d.ts.map +1 -1
- package/dist/memory/entry.js +1 -2
- package/dist/memory/entry.js.map +1 -1
- package/dist/memory/frontmatter-edit.d.ts +16 -4
- package/dist/memory/frontmatter-edit.d.ts.map +1 -1
- package/dist/memory/frontmatter-edit.js +13 -1
- package/dist/memory/frontmatter-edit.js.map +1 -1
- package/dist/memory/git-log.d.ts +18 -6
- package/dist/memory/git-log.d.ts.map +1 -1
- package/dist/memory/git-log.js +40 -13
- package/dist/memory/git-log.js.map +1 -1
- package/dist/memory/history.d.ts +74 -13
- package/dist/memory/history.d.ts.map +1 -1
- package/dist/memory/history.js +129 -45
- package/dist/memory/history.js.map +1 -1
- package/dist/memory/index.d.ts +13 -11
- package/dist/memory/index.d.ts.map +1 -1
- package/dist/memory/index.js +38 -3
- package/dist/memory/index.js.map +1 -1
- package/dist/memory/query.d.ts +161 -19
- package/dist/memory/query.d.ts.map +1 -1
- package/dist/memory/query.js +435 -55
- package/dist/memory/query.js.map +1 -1
- package/dist/memory/schema.d.ts +52 -3
- package/dist/memory/schema.d.ts.map +1 -1
- package/dist/memory/schema.js +132 -8
- package/dist/memory/schema.js.map +1 -1
- package/dist/memory/state-machine.d.ts +65 -10
- package/dist/memory/state-machine.d.ts.map +1 -1
- package/dist/memory/state-machine.js +136 -48
- package/dist/memory/state-machine.js.map +1 -1
- package/dist/memory/submit.d.ts +52 -3
- package/dist/memory/submit.d.ts.map +1 -1
- package/dist/memory/submit.js +118 -11
- package/dist/memory/submit.js.map +1 -1
- package/dist/storage/build-stamp.d.ts +18 -0
- package/dist/storage/build-stamp.d.ts.map +1 -0
- package/dist/storage/build-stamp.js +68 -0
- package/dist/storage/build-stamp.js.map +1 -0
- package/dist/storage/builtin-adapters.d.ts +32 -0
- package/dist/storage/builtin-adapters.d.ts.map +1 -0
- package/dist/storage/builtin-adapters.js +100 -0
- package/dist/storage/builtin-adapters.js.map +1 -0
- package/dist/storage/builtin-directives.d.ts +0 -29
- package/dist/storage/builtin-directives.d.ts.map +1 -1
- package/dist/storage/builtin-directives.js +9 -4
- package/dist/storage/builtin-directives.js.map +1 -1
- package/dist/storage/commit.d.ts +310 -3
- package/dist/storage/commit.d.ts.map +1 -1
- package/dist/storage/commit.js +807 -33
- package/dist/storage/commit.js.map +1 -1
- package/dist/storage/document.d.ts +6 -0
- package/dist/storage/document.d.ts.map +1 -1
- package/dist/storage/document.js +10 -1
- package/dist/storage/document.js.map +1 -1
- package/dist/storage/dry-run.d.ts +65 -0
- package/dist/storage/dry-run.d.ts.map +1 -0
- package/dist/storage/dry-run.js +150 -0
- package/dist/storage/dry-run.js.map +1 -0
- package/dist/storage/errors.d.ts +26 -0
- package/dist/storage/errors.d.ts.map +1 -1
- package/dist/storage/errors.js +27 -1
- package/dist/storage/errors.js.map +1 -1
- package/dist/storage/frontmatter.d.ts +1 -1
- package/dist/storage/frontmatter.js +1 -1
- package/dist/storage/git-read.d.ts +64 -0
- package/dist/storage/git-read.d.ts.map +1 -0
- package/dist/storage/git-read.js +107 -0
- package/dist/storage/git-read.js.map +1 -0
- package/dist/storage/index.d.ts +13 -5
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +39 -2
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/layout.d.ts +2 -2
- package/dist/storage/layout.d.ts.map +1 -1
- package/dist/storage/layout.js +2 -7
- package/dist/storage/layout.js.map +1 -1
- package/dist/storage/memory-path.d.ts +18 -5
- package/dist/storage/memory-path.d.ts.map +1 -1
- package/dist/storage/memory-path.js +41 -5
- package/dist/storage/memory-path.js.map +1 -1
- package/dist/storage/snapshot.js +1 -1
- package/dist/storage/templates.d.ts +33 -6
- package/dist/storage/templates.d.ts.map +1 -1
- package/dist/storage/templates.js +182 -17
- package/dist/storage/templates.js.map +1 -1
- package/dist/validation/config-version.d.ts +19 -0
- package/dist/validation/config-version.d.ts.map +1 -0
- package/dist/validation/config-version.js +53 -0
- package/dist/validation/config-version.js.map +1 -0
- package/dist/validation/diagnostic.d.ts +40 -0
- package/dist/validation/diagnostic.d.ts.map +1 -0
- package/dist/validation/diagnostic.js +41 -0
- package/dist/validation/diagnostic.js.map +1 -0
- package/dist/validation/format.d.ts +74 -0
- package/dist/validation/format.d.ts.map +1 -0
- package/dist/validation/format.js +104 -0
- package/dist/validation/format.js.map +1 -0
- package/dist/validation/id.d.ts +40 -0
- package/dist/validation/id.d.ts.map +1 -1
- package/dist/validation/id.js +99 -12
- package/dist/validation/id.js.map +1 -1
- package/dist/validation/identity.d.ts +40 -0
- package/dist/validation/identity.d.ts.map +1 -0
- package/dist/validation/identity.js +78 -0
- package/dist/validation/identity.js.map +1 -0
- package/dist/validation/index.d.ts +9 -3
- package/dist/validation/index.d.ts.map +1 -1
- package/dist/validation/index.js +33 -1
- package/dist/validation/index.js.map +1 -1
- package/dist/validation/secret-scan.d.ts +10 -7
- package/dist/validation/secret-scan.d.ts.map +1 -1
- package/dist/validation/secret-scan.js +39 -16
- package/dist/validation/secret-scan.js.map +1 -1
- package/dist/validation/warning.d.ts +30 -16
- package/dist/validation/warning.d.ts.map +1 -1
- package/dist/validation/warning.js +74 -7
- package/dist/validation/warning.js.map +1 -1
- package/dist/workflow/bindings.d.ts +152 -0
- package/dist/workflow/bindings.d.ts.map +1 -0
- package/dist/workflow/bindings.js +249 -0
- package/dist/workflow/bindings.js.map +1 -0
- package/dist/workflow/create.d.ts +13 -0
- package/dist/workflow/create.d.ts.map +1 -0
- package/dist/workflow/create.js +41 -0
- package/dist/workflow/create.js.map +1 -0
- package/dist/workflow/deduce.d.ts +400 -0
- package/dist/workflow/deduce.d.ts.map +1 -0
- package/dist/workflow/deduce.js +1109 -0
- package/dist/workflow/deduce.js.map +1 -0
- package/dist/workflow/manifest-edit.d.ts +19 -0
- package/dist/workflow/manifest-edit.d.ts.map +1 -0
- package/dist/workflow/manifest-edit.js +124 -0
- package/dist/workflow/manifest-edit.js.map +1 -0
- package/dist/workflow/schema.d.ts +158 -19
- package/dist/workflow/schema.d.ts.map +1 -1
- package/dist/workflow/schema.js +190 -16
- package/dist/workflow/schema.js.map +1 -1
- package/package.json +37 -13
|
@@ -0,0 +1,1109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PLAN_TYPE = exports.OPEN_PLAN_STATUSES = exports.NO_ITERATION_NOTE = exports.W_INSTANCE_WORKFLOW_UNKNOWN = exports.W_UNRESOLVED_TOKEN = exports.W_MEMORY_INVALID_STATE = exports.W_UNCOMMITTED_INPUTS = void 0;
|
|
4
|
+
exports.uncommittedInputDiagnostic = uncommittedInputDiagnostic;
|
|
5
|
+
exports.declaresState = declaresState;
|
|
6
|
+
exports.deduceWorkflowState = deduceWorkflowState;
|
|
7
|
+
exports.resolveInstanceRef = resolveInstanceRef;
|
|
8
|
+
const query_1 = require("../memory/query");
|
|
9
|
+
const state_machine_1 = require("../memory/state-machine");
|
|
10
|
+
const validation_1 = require("../validation");
|
|
11
|
+
// The modules, not the `../core` barrel: `src/core` imports this one.
|
|
12
|
+
const workflow_diagnostics_1 = require("../core/workflow-diagnostics");
|
|
13
|
+
const workflow_exit_state_1 = require("../core/workflow-exit-state");
|
|
14
|
+
const bindings_1 = require("./bindings");
|
|
15
|
+
const schema_1 = require("./schema");
|
|
16
|
+
/** `W_UNCOMMITTED_INPUTS` (`spec-017` §1.2): a deduction input differs from `HEAD`; the answer is `HEAD`'s. */
|
|
17
|
+
exports.W_UNCOMMITTED_INPUTS = 'W_UNCOMMITTED_INPUTS';
|
|
18
|
+
/**
|
|
19
|
+
* The `W_UNCOMMITTED_INPUTS` diagnostic of one dirty input path (`spec-017` §1.2; one per path, task-198).
|
|
20
|
+
* The deduction builds its own through here, and so does `agent list --past`, which runs no deduction and
|
|
21
|
+
* emits the code itself under the same rule (`spec-016` §5, task-240), so the two cannot word it apart.
|
|
22
|
+
*/
|
|
23
|
+
function uncommittedInputDiagnostic(path) {
|
|
24
|
+
return {
|
|
25
|
+
code: exports.W_UNCOMMITTED_INPUTS,
|
|
26
|
+
severity: 'warning',
|
|
27
|
+
file: path,
|
|
28
|
+
path: '',
|
|
29
|
+
message: `uncommitted change to a deduction input, not read (the answer is computed from HEAD): ${path}`,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** `W_MEMORY_INVALID_STATE` (`spec-017` §1.4, P4.13 sc. 3): a status its type's machine does not declare. */
|
|
33
|
+
exports.W_MEMORY_INVALID_STATE = 'W_MEMORY_INVALID_STATE';
|
|
34
|
+
/** `W_UNRESOLVED_TOKEN` (`spec-017` §4.1): a token with no value on the element it resolves against. */
|
|
35
|
+
exports.W_UNRESOLVED_TOKEN = 'W_UNRESOLVED_TOKEN';
|
|
36
|
+
/** `W_INSTANCE_WORKFLOW_UNKNOWN` (`spec-017` §2): an open plan names a workflow the registry does not load. */
|
|
37
|
+
exports.W_INSTANCE_WORKFLOW_UNKNOWN = 'W_INSTANCE_WORKFLOW_UNKNOWN';
|
|
38
|
+
/** The note of an `iterate_over` phase no candidate was counted for (`spec-017` §4.6, P4.16 sc. 3). */
|
|
39
|
+
exports.NO_ITERATION_NOTE = 'no elements matched the iterate_over filter';
|
|
40
|
+
/** The plan statuses of an open instance (`spec-017` §3.2). */
|
|
41
|
+
exports.OPEN_PLAN_STATUSES = ['draft', 'active'];
|
|
42
|
+
/** The Memory type that records a workflow instance (`spec-017` §3.1, `dl-019`). */
|
|
43
|
+
exports.PLAN_TYPE = 'plan';
|
|
44
|
+
/** Code-unit order (`Array.prototype.sort`'s default), as a comparator. */
|
|
45
|
+
function compareText(a, b) {
|
|
46
|
+
return Number(a > b) - Number(a < b);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* A `memory.yaml` type `path` pattern as a whole-path regular expression. A token in the file name is one
|
|
50
|
+
* name; a token in a directory is one or more directories, since a value such as the plan's `{scope}`
|
|
51
|
+
* nests (`rl-v1/rel-v0.3`, `.wingfoil/memory.yaml` `plan.path`).
|
|
52
|
+
*/
|
|
53
|
+
function pathPatternRegExp(pattern) {
|
|
54
|
+
const slash = pattern.lastIndexOf('/');
|
|
55
|
+
const convert = (part, token) => part
|
|
56
|
+
.split(/\{[^{}]+\}/)
|
|
57
|
+
.map((piece) => piece.replace(/[.*+?^$()|[\]\\]/g, '\\$&'))
|
|
58
|
+
.join(token);
|
|
59
|
+
return new RegExp(`^${convert(pattern.slice(0, slash + 1), '[^/]+(?:/[^/]+)*')}${convert(pattern.slice(slash + 1), '[^/]+')}$`);
|
|
60
|
+
}
|
|
61
|
+
/** A frontmatter value as a non-blank string, or `null`. */
|
|
62
|
+
function scalarText(value) {
|
|
63
|
+
if (typeof value === 'string')
|
|
64
|
+
return value.trim() === '' ? null : value;
|
|
65
|
+
if (typeof value === 'number' || typeof value === 'boolean')
|
|
66
|
+
return String(value);
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
function hasType(memoryYaml, type) {
|
|
70
|
+
return memoryYaml !== null && Object.prototype.hasOwnProperty.call(memoryYaml.types, type);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The elements of the snapshot, and the `W_MEMORY_*` reports of the documents left out (§1.4): no
|
|
74
|
+
* frontmatter, no `type`, a type `memory.yaml` does not declare, no `id`, no `status` —
|
|
75
|
+
* `W_MEMORY_UNREADABLE`; a status the type's machine does not declare — `W_MEMORY_INVALID_STATE`,
|
|
76
|
+
* decided by `validateFrontmatterState`, the rule every Memory verb applies (P4.11).
|
|
77
|
+
*/
|
|
78
|
+
function readElements(snapshot) {
|
|
79
|
+
const elements = [];
|
|
80
|
+
const diagnostics = [...snapshot.scanDiagnostics];
|
|
81
|
+
const { memoryYaml } = snapshot;
|
|
82
|
+
const typePaths = Object.values(memoryYaml?.types ?? {}).map((entry) => pathPatternRegExp(entry.path));
|
|
83
|
+
for (const document of snapshot.documents) {
|
|
84
|
+
const { path, frontmatter } = document;
|
|
85
|
+
const unreadable = (reason) => {
|
|
86
|
+
diagnostics.push((0, query_1.memoryUnreadableDiagnostic)(path, reason));
|
|
87
|
+
};
|
|
88
|
+
if (Object.keys(frontmatter).length === 0) {
|
|
89
|
+
// A file on no type's path is not a Memory document at all — the `X_*` plans `dl-019` grandfathers at
|
|
90
|
+
// the top of `docs/05_plans/` — and is left out silently; one on a type's path is a document §1.4 reports.
|
|
91
|
+
if (typePaths.some((pattern) => pattern.test(path)))
|
|
92
|
+
unreadable('no frontmatter');
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const type = scalarText(frontmatter['type']);
|
|
96
|
+
if (type === null) {
|
|
97
|
+
unreadable("no 'type' field");
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (!hasType(memoryYaml, type)) {
|
|
101
|
+
unreadable(`type '${type}' is not declared in memory.yaml`);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const id = scalarText(frontmatter['id']);
|
|
105
|
+
if (id === null) {
|
|
106
|
+
unreadable("no 'id' field");
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const status = scalarText(frontmatter['status']);
|
|
110
|
+
if (status === null) {
|
|
111
|
+
unreadable("no 'status' field");
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
(0, state_machine_1.validateFrontmatterState)((0, state_machine_1.resolveStateMachine)(memoryYaml, type), type, status, path);
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
const issue = error.issues[0];
|
|
119
|
+
diagnostics.push({ code: exports.W_MEMORY_INVALID_STATE, severity: 'warning', file: path, path: 'status', message: `${issue.message} in ${path}` });
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
elements.push({ type, id, status, path, frontmatter });
|
|
123
|
+
}
|
|
124
|
+
// By path; a stable sort keeps one file's reports in the order they were made.
|
|
125
|
+
diagnostics.sort((a, b) => compareText(a.file, b.file));
|
|
126
|
+
return { elements, diagnostics };
|
|
127
|
+
}
|
|
128
|
+
/** `<type>:<id>` of an element scope, `<collection>#<key>` of a collection-entry scope (`spec-017` §4.8, §4.9). */
|
|
129
|
+
function scopeKey(scope) {
|
|
130
|
+
if ('item' in scope)
|
|
131
|
+
return `${scope.item.collection}#${scope.item.key}`;
|
|
132
|
+
return `${scope.element.type}:${scope.element.id}`;
|
|
133
|
+
}
|
|
134
|
+
function refOf(element) {
|
|
135
|
+
return { type: element.type, id: element.id, status: element.status };
|
|
136
|
+
}
|
|
137
|
+
/** Ascending `(type, id)` (`spec-017` §1.3, "elements a step created or awaits"). */
|
|
138
|
+
function compareRefs(a, b) {
|
|
139
|
+
return compareText(a.type, b.type) || compareText(a.id, b.id);
|
|
140
|
+
}
|
|
141
|
+
/** The scope a step runs on: its innermost frame's element or collection entry (§4.9). */
|
|
142
|
+
function scopeOf(frames) {
|
|
143
|
+
const frame = frames[frames.length - 1];
|
|
144
|
+
if (frame === undefined)
|
|
145
|
+
return null;
|
|
146
|
+
if (frame.kind === 'item')
|
|
147
|
+
return { item: { collection: frame.collection, key: frame.key } };
|
|
148
|
+
return frame.element === null ? null : { element: refOf(frame.element) };
|
|
149
|
+
}
|
|
150
|
+
const TOKEN_RE = /\{([^{}]+)\}/g;
|
|
151
|
+
/**
|
|
152
|
+
* A collection reference (`dna:<path>`, `bindings:<name>`, `element:<field>`) rather than a Memory type
|
|
153
|
+
* (`spec-003` § "Collections"; `element:` since task-230).
|
|
154
|
+
*/
|
|
155
|
+
const COLLECTION_REF_RE = /^(dna|bindings|element):/;
|
|
156
|
+
/** A list read from a frontmatter field of the innermost bound element (`spec-017` §4.6, task-230). */
|
|
157
|
+
const ELEMENT_COLLECTION = 'element:';
|
|
158
|
+
/**
|
|
159
|
+
* The entries of an `element:<field>` collection (`spec-017` §4.6): the list in `<field>` of the innermost
|
|
160
|
+
* bound element, labelled `<type>:<id>/<field>`; an explicit `[]` has no entries. No bound element, an absent
|
|
161
|
+
* field, a value that is not a list, or an entry key problem (`collectionKeyIssues`, the rule `bindings.yaml` collections
|
|
162
|
+
* are loaded under) leaves the entries undecided, with the reason.
|
|
163
|
+
*/
|
|
164
|
+
function elementCollection(reference, frames) {
|
|
165
|
+
const field = reference.slice(ELEMENT_COLLECTION.length);
|
|
166
|
+
const frame = [...frames].reverse().find((candidate) => candidate.kind === 'element');
|
|
167
|
+
if (frame === undefined)
|
|
168
|
+
return { reason: `collection '${reference}' has no element in scope` };
|
|
169
|
+
if (frame.element === null)
|
|
170
|
+
return { reason: `collection '${reference}' has no ${frame.type} element bound` };
|
|
171
|
+
const ref = `${frame.element.type}:${frame.element.id}`;
|
|
172
|
+
const raw = frame.element.frontmatter[field];
|
|
173
|
+
// An absent field is undecided, not empty (task-230 review F1): a release planned before its waves were declared
|
|
174
|
+
// must not read as a finished implementation. An explicit `[]` is a declared empty list (vacuous, §4.6).
|
|
175
|
+
if (raw === undefined || raw === null)
|
|
176
|
+
return { reason: `collection '${reference}' of ${ref} has no field '${field}'` };
|
|
177
|
+
if (!Array.isArray(raw))
|
|
178
|
+
return { reason: `collection '${reference}' of ${ref} is not a list` };
|
|
179
|
+
const entries = raw;
|
|
180
|
+
const issue = (0, bindings_1.collectionKeyIssues)(entries)[0];
|
|
181
|
+
if (issue !== undefined)
|
|
182
|
+
return { reason: `collection '${reference}' of ${ref} entry ${issue.index}: ${issue.message}` };
|
|
183
|
+
return { label: `${ref}/${field}`, entries };
|
|
184
|
+
}
|
|
185
|
+
/** `{item}` / `{item.<field>}` against the innermost collection entry (§4.1, `spec-003` § "Collections"). */
|
|
186
|
+
function itemValue(inner, frames) {
|
|
187
|
+
const frame = [...frames].reverse().find((candidate) => candidate.kind === 'item');
|
|
188
|
+
if (frame === undefined)
|
|
189
|
+
return { reason: 'no collection entry in scope' };
|
|
190
|
+
if (inner === 'item')
|
|
191
|
+
return { value: frame.key };
|
|
192
|
+
const field = inner.slice('item.'.length);
|
|
193
|
+
const raw = typeof frame.entry === 'object' && frame.entry !== null ? scalarText(frame.entry[field]) : null;
|
|
194
|
+
return raw === null ? { reason: `${frame.collection}#${frame.key} has no value for '${field}'` } : { value: raw };
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Substitute the `{…}` tokens of `text` against `frames` (innermost last), as whole values, never
|
|
198
|
+
* evaluated (`dl-090` Q3 (a)): `{<type>.<field>}` against the nearest element of `<type>`; `{id}`,
|
|
199
|
+
* `{<field>}` and `{element.<field>}` against the innermost element; `{item}` and `{item.<field>}`
|
|
200
|
+
* against the innermost collection entry.
|
|
201
|
+
*/
|
|
202
|
+
function resolveTokens(text, frames) {
|
|
203
|
+
const unresolved = [];
|
|
204
|
+
let pending = false;
|
|
205
|
+
const elementFrames = frames.filter((frame) => frame.kind === 'element');
|
|
206
|
+
const value = text.replace(TOKEN_RE, (token, inner) => {
|
|
207
|
+
let frame;
|
|
208
|
+
let field;
|
|
209
|
+
const dot = inner.indexOf('.');
|
|
210
|
+
if (inner === 'item' || inner.startsWith('item.')) {
|
|
211
|
+
const item = itemValue(inner, frames);
|
|
212
|
+
if ('value' in item)
|
|
213
|
+
return item.value;
|
|
214
|
+
unresolved.push({ token, reason: item.reason });
|
|
215
|
+
return token;
|
|
216
|
+
}
|
|
217
|
+
if (dot === -1) {
|
|
218
|
+
frame = elementFrames[elementFrames.length - 1];
|
|
219
|
+
field = inner;
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
const head = inner.slice(0, dot);
|
|
223
|
+
field = inner.slice(dot + 1);
|
|
224
|
+
frame = head === 'element' ? elementFrames[elementFrames.length - 1] : [...elementFrames].reverse().find((candidate) => candidate.type === head);
|
|
225
|
+
}
|
|
226
|
+
if (frame === undefined) {
|
|
227
|
+
unresolved.push({ token, reason: 'no element in scope' });
|
|
228
|
+
return token;
|
|
229
|
+
}
|
|
230
|
+
if (frame.element === null) {
|
|
231
|
+
if (frame.pending)
|
|
232
|
+
pending = true;
|
|
233
|
+
else
|
|
234
|
+
unresolved.push({ token, reason: `no ${frame.type} element is bound` });
|
|
235
|
+
return token;
|
|
236
|
+
}
|
|
237
|
+
const raw = field === 'id' ? frame.element.id : scalarText(frame.element.frontmatter[field]);
|
|
238
|
+
if (raw === null) {
|
|
239
|
+
unresolved.push({ token, reason: `${frame.element.type}:${frame.element.id} has no value for '${field}'` });
|
|
240
|
+
return token;
|
|
241
|
+
}
|
|
242
|
+
return raw;
|
|
243
|
+
});
|
|
244
|
+
return { value: unresolved.length === 0 && !pending ? value : null, unresolved, pending };
|
|
245
|
+
}
|
|
246
|
+
// --- Evidence (§4.3) ---------------------------------------------------------------------------------
|
|
247
|
+
/** The untyped verbs that, before any `memory.add` and outside a selection, move the bound element (`spec-003` § "Evidence"). */
|
|
248
|
+
const STATE_VERBS = ['memory.submit', 'memory.approve', 'element.set_state'];
|
|
249
|
+
/** The untyped tokens that act on the created elements, the selection or the bound element (`spec-017` §4.2). */
|
|
250
|
+
const UNTYPED_TARGET_VERBS = ['memory.submit', 'memory.approve', 'memory.reject', 'memory.deprecate', 'element.set_state', 'element.set_release'];
|
|
251
|
+
/** A frontmatter value as a list of strings (absent: none). */
|
|
252
|
+
function asStrings(value) {
|
|
253
|
+
if (value === undefined || value === null)
|
|
254
|
+
return [];
|
|
255
|
+
return (Array.isArray(value) ? value : [value]).map(String);
|
|
256
|
+
}
|
|
257
|
+
const TYPED_SET_STATE_RE = /^([a-z][a-z0-9-]*)\.set_state$/;
|
|
258
|
+
/** Whether `phase` declares `state` evidence on an element of `boundType` (`spec-003` § "Evidence", `spec-017` §4.2). */
|
|
259
|
+
function declaresState(phase, boundType) {
|
|
260
|
+
if (boundType === null)
|
|
261
|
+
return false;
|
|
262
|
+
const selection = phase.where !== undefined && phase.iterate_over === undefined;
|
|
263
|
+
let added = false;
|
|
264
|
+
for (const action of phase.actions ?? []) {
|
|
265
|
+
const name = (0, bindings_1.tokenName)(action);
|
|
266
|
+
if (name === 'memory.add') {
|
|
267
|
+
added = true;
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
if (STATE_VERBS.includes(name) && !added && !selection)
|
|
271
|
+
return true;
|
|
272
|
+
if (TYPED_SET_STATE_RE.exec(name)?.[1] === boundType)
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
/** Whether `status` is at or after `exit` in `machine`'s `sequence` (§4.3 `state`, §4.4). */
|
|
278
|
+
function atOrAfter(machine, status, exit) {
|
|
279
|
+
const at = machine.sequence.indexOf(status);
|
|
280
|
+
const target = machine.sequence.indexOf(exit);
|
|
281
|
+
if (at === -1 || target === -1)
|
|
282
|
+
return status === exit;
|
|
283
|
+
return at >= target;
|
|
284
|
+
}
|
|
285
|
+
/** `spec-003`'s `where` match rule: equal, member, or (for a list field) a shared element. Absent reads as `""`. */
|
|
286
|
+
function matches(field, wanted) {
|
|
287
|
+
const have = field === undefined || field === null ? [''] : Array.isArray(field) ? field.map(String) : [String(field)];
|
|
288
|
+
return have.some((value) => wanted.includes(value));
|
|
289
|
+
}
|
|
290
|
+
/** The history of an instance with nothing in its walk. */
|
|
291
|
+
const EMPTY_HISTORY = { records: [], links: [], reentries: [] };
|
|
292
|
+
/** `<type>:<id>` of an element. */
|
|
293
|
+
function keyOf(element) {
|
|
294
|
+
return `${element.type}:${element.id}`;
|
|
295
|
+
}
|
|
296
|
+
/** Ascending `(type, id)` (`spec-017` §1.3, "elements a step created"). */
|
|
297
|
+
function byTypeThenId(a, b) {
|
|
298
|
+
return compareText(a.type, b.type) || compareText(a.id, b.id);
|
|
299
|
+
}
|
|
300
|
+
/** The newest of the re-entries that reach a step (the smallest walk position), for display; `null` for none. */
|
|
301
|
+
function newest(reentries) {
|
|
302
|
+
return reentries.reduce((best, entry) => (best === null || entry.position < best.position ? entry : best), null);
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* The carried elements that await a phase's approval (`spec-017` §5.1): the targets of its actions — the bound
|
|
306
|
+
* element, the selection, the created and the run elements; a `sync_state` is never a carrier (§4.2) — that sit
|
|
307
|
+
* in a gate state the phase holds, each once, ascending `(type, id)`.
|
|
308
|
+
*/
|
|
309
|
+
function awaitingElements(actions, exit) {
|
|
310
|
+
const found = new Map();
|
|
311
|
+
for (const action of actions) {
|
|
312
|
+
if (action.target === 'none' || (0, bindings_1.tokenName)(action.token).endsWith('.sync_state'))
|
|
313
|
+
continue;
|
|
314
|
+
for (const target of action.targets) {
|
|
315
|
+
if (exit.held.some((held) => held.type === target.type && held.gate === target.status))
|
|
316
|
+
found.set(keyOf(target), target);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return [...found.values()].sort(compareRefs);
|
|
320
|
+
}
|
|
321
|
+
/** The read-only context of one deduction. */
|
|
322
|
+
class Deducer {
|
|
323
|
+
snapshot;
|
|
324
|
+
byName = new Map();
|
|
325
|
+
fileOf = new Map();
|
|
326
|
+
tree;
|
|
327
|
+
elements;
|
|
328
|
+
/** The walk's child links (the inverse of `snapshot.parents`), built on first use. */
|
|
329
|
+
children = null;
|
|
330
|
+
/** The strict descendants in the walk of each re-entry commit asked about. */
|
|
331
|
+
descendantsOf = new Map();
|
|
332
|
+
constructor(snapshot, elements) {
|
|
333
|
+
this.snapshot = snapshot;
|
|
334
|
+
// Names are unique: the registry refuses a duplicate at load time (`spec-003`), so no entry is overwritten.
|
|
335
|
+
snapshot.workflows.forEach((workflow, i) => {
|
|
336
|
+
this.byName.set(workflow.name, workflow);
|
|
337
|
+
this.fileOf.set(workflow.name, snapshot.workflowFiles[i]);
|
|
338
|
+
});
|
|
339
|
+
this.tree = new Set(snapshot.tree);
|
|
340
|
+
this.elements = elements;
|
|
341
|
+
}
|
|
342
|
+
workflow(name) {
|
|
343
|
+
return this.byName.get(name);
|
|
344
|
+
}
|
|
345
|
+
/** The first element of `type` with `id`, in path order. */
|
|
346
|
+
find(type, id) {
|
|
347
|
+
return this.elements.find((element) => element.type === type && element.id === id) ?? null;
|
|
348
|
+
}
|
|
349
|
+
/** The first element of any type with `id`, in path order. */
|
|
350
|
+
findAny(id) {
|
|
351
|
+
return this.elements.find((element) => element.id === id) ?? null;
|
|
352
|
+
}
|
|
353
|
+
plans() {
|
|
354
|
+
return this.elements.filter((element) => element.type === exports.PLAN_TYPE);
|
|
355
|
+
}
|
|
356
|
+
exists(path) {
|
|
357
|
+
if (!path.endsWith('/'))
|
|
358
|
+
return this.tree.has(path);
|
|
359
|
+
return this.snapshot.tree.some((file) => file.startsWith(path));
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* The first state of `type`. Called only to bind an instance, i.e. for a plan element, which exists only
|
|
363
|
+
* when `memory.yaml` does; and a workflow's `element` type is one `memory.yaml` declares, or the registry
|
|
364
|
+
* refuses it (`E_WORKFLOW_ELEMENT_TYPE_UNKNOWN`).
|
|
365
|
+
*/
|
|
366
|
+
firstState(type) {
|
|
367
|
+
return (0, state_machine_1.resolveStateMachine)(this.snapshot.memoryYaml, type).sequence[0];
|
|
368
|
+
}
|
|
369
|
+
/** What the walk of the instance started at `startCommit` holds (§4.8). */
|
|
370
|
+
history(startCommit) {
|
|
371
|
+
return this.snapshot.history.get(startCommit) ?? EMPTY_HISTORY;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* The elements an instance's steps created (§4.8 linkage): the add commits of its walk whose
|
|
375
|
+
* `WingFoil-Instance` is the instance and whose `WingFoil-Step` is one of `steps` (every step when
|
|
376
|
+
* `null`), each element once, ascending `(type, id)`. An element `HEAD` does not hold is left out.
|
|
377
|
+
*/
|
|
378
|
+
createdBy(instanceId, startCommit, steps) {
|
|
379
|
+
const found = new Map();
|
|
380
|
+
for (const link of this.history(startCommit).links) {
|
|
381
|
+
if (link.instance !== instanceId || (steps !== null && !steps.includes(link.step)))
|
|
382
|
+
continue;
|
|
383
|
+
const element = this.find(link.type, link.id);
|
|
384
|
+
if (element !== null)
|
|
385
|
+
found.set(keyOf(element), element);
|
|
386
|
+
}
|
|
387
|
+
return [...found.values()].sort(byTypeThenId);
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* The commit that put `element` in its current status (§4.8, `state` after a re-entry): the latest
|
|
391
|
+
* transition commit naming it that left that status — its bracket's `<to>`, or the file's status at a
|
|
392
|
+
* bracketless `add` / `submit` (task-281, `bug-300`) — else the latest commit in the walk that changed its
|
|
393
|
+
* file; `null` (older than anything in the walk) when neither.
|
|
394
|
+
*/
|
|
395
|
+
stateCommit(element) {
|
|
396
|
+
const named = this.snapshot.transitions.find((entry) => entry.type === element.type && entry.id === element.id && entry.to === element.status);
|
|
397
|
+
return named?.commit ?? this.snapshot.lastChanges.get(keyOf(element))?.commit ?? null;
|
|
398
|
+
}
|
|
399
|
+
/** The strict descendants of `commit` within the walk, through its parent links (§4.8, review F1). */
|
|
400
|
+
descendants(commit) {
|
|
401
|
+
const known = this.descendantsOf.get(commit);
|
|
402
|
+
if (known !== undefined)
|
|
403
|
+
return known;
|
|
404
|
+
if (this.children === null) {
|
|
405
|
+
this.children = new Map();
|
|
406
|
+
for (const [child, parents] of this.snapshot.parents) {
|
|
407
|
+
for (const parent of parents)
|
|
408
|
+
this.children.set(parent, [...(this.children.get(parent) ?? []), child]);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
const found = new Set();
|
|
412
|
+
const pending = [...(this.children.get(commit) ?? [])];
|
|
413
|
+
while (pending.length > 0) {
|
|
414
|
+
const next = pending.pop();
|
|
415
|
+
if (found.has(next))
|
|
416
|
+
continue;
|
|
417
|
+
found.add(next);
|
|
418
|
+
pending.push(...(this.children.get(next) ?? []));
|
|
419
|
+
}
|
|
420
|
+
this.descendantsOf.set(commit, found);
|
|
421
|
+
return found;
|
|
422
|
+
}
|
|
423
|
+
/** Whether `commit` is newer than every re-entry in `reentries`: a strict descendant of each (§4.8). */
|
|
424
|
+
newerThan(commit, reentries) {
|
|
425
|
+
return reentries.every((reentry) => commit !== null && this.descendants(reentry.commit).has(commit));
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* The re-entry cutoff of each phase of a pass of `workflow` on `element` (§4.8 re-entries, §5.2): for
|
|
429
|
+
* every re-entry of the element in the instance's walk, the phase whose gate state was its `<from>` —
|
|
430
|
+
* the first phase that holds that gate (§5.1) and declares a `fallback` — names a `fallback.step`; from
|
|
431
|
+
* that phase onward the `record` and `state` evidence counts only if newer than the re-entry — a
|
|
432
|
+
* descendant of its commit. A phase reached by several re-entries keeps them all, and its evidence must
|
|
433
|
+
* descend from each; `inherited` is what reaches the including phase. A
|
|
434
|
+
* re-entry from a state no phase holds as a gate with a fallback reaches no phase: the element's state
|
|
435
|
+
* alone tells the deduction where the pass stands.
|
|
436
|
+
*/
|
|
437
|
+
cutoffs(startCommit, workflow, exits, element, inherited) {
|
|
438
|
+
const out = workflow.phases.map(() => [...inherited]);
|
|
439
|
+
if (element === null)
|
|
440
|
+
return out;
|
|
441
|
+
for (const reentry of this.history(startCommit).reentries) {
|
|
442
|
+
if (reentry.type !== element.type || reentry.id !== element.id)
|
|
443
|
+
continue;
|
|
444
|
+
const gate = workflow.phases.findIndex((phase, p) => phase.fallback !== undefined && exits[p].held.some((held) => held.type === reentry.type && held.gate === reentry.from));
|
|
445
|
+
if (gate === -1)
|
|
446
|
+
continue;
|
|
447
|
+
// A `fallback.step` naming no phase is refused at load time (`E_PHASE_FALLBACK_STEP_UNKNOWN`, spec-003).
|
|
448
|
+
const fallback = workflow.phases[gate].fallback;
|
|
449
|
+
const from = workflow.phases.findIndex((phase) => phase.name === fallback.step);
|
|
450
|
+
// Each reaching re-entry names the fallback that took it, which the re-entered step reports (§5.2).
|
|
451
|
+
const reaching = { ...reentry, fallback: { step: fallback.step, setState: fallback.set_state ?? null } };
|
|
452
|
+
for (let q = from; q < out.length; q += 1)
|
|
453
|
+
out[q].push(reaching);
|
|
454
|
+
}
|
|
455
|
+
return out;
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Run a workflow on `frames`, from `start` (§4.4), under `trail`; `inherited` are the re-entries reaching
|
|
459
|
+
* the including phase (§4.8). In declared order, a phase is complete
|
|
460
|
+
* when its evidence is; a live query (§4.7) is also complete, and an optional phase (§4.10) skipped,
|
|
461
|
+
* when a later phase is complete other than vacuously; the first other phase is current and gives the
|
|
462
|
+
* frontier. A phase is evaluated only when one of these rules needs it, at most once.
|
|
463
|
+
*/
|
|
464
|
+
run(instanceId, startCommit, workflow, frames, start, trail, inherited = []) {
|
|
465
|
+
// `memory.yaml` is present: a workflow runs only for an open plan, an element of a declared type.
|
|
466
|
+
const exits = (0, workflow_exit_state_1.workflowExitStates)(workflow, this.snapshot.memoryYaml, start, this.byName);
|
|
467
|
+
const last = frames[frames.length - 1];
|
|
468
|
+
// A collection entry has no re-entries: only an element scope cuts evidence (§4.8).
|
|
469
|
+
const cutoffs = this.cutoffs(startCommit, workflow, exits, last?.kind === 'element' ? last.element : null, inherited);
|
|
470
|
+
const scope = scopeOf(frames);
|
|
471
|
+
const evals = [];
|
|
472
|
+
const evaluated = (p) => (evals[p] ??= this.evaluate(instanceId, startCommit, workflow, p, frames, exits[p], [...trail, { workflow: workflow.name, phase: workflow.phases[p].name, scope }], scope, cutoffs[p]));
|
|
473
|
+
const closedFrom = (p) => {
|
|
474
|
+
for (let q = p + 1; q < workflow.phases.length; q += 1) {
|
|
475
|
+
const later = evaluated(q);
|
|
476
|
+
if (later.complete && !later.vacuous)
|
|
477
|
+
return true;
|
|
478
|
+
}
|
|
479
|
+
return false;
|
|
480
|
+
};
|
|
481
|
+
const phases = [];
|
|
482
|
+
let frontier = [];
|
|
483
|
+
let diagnostics = [];
|
|
484
|
+
const late = [];
|
|
485
|
+
let current = false;
|
|
486
|
+
let progressed = false;
|
|
487
|
+
workflow.phases.forEach((phase, p) => {
|
|
488
|
+
if (current) {
|
|
489
|
+
phases.push({ phase: phase.name, state: 'pending' });
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
const evaluation = evaluated(p);
|
|
493
|
+
if (evaluation.complete) {
|
|
494
|
+
phases.push(progress(phase.name, 'complete', evaluation.vacuous, evaluation.iterations));
|
|
495
|
+
late.push(...evaluation.late);
|
|
496
|
+
progressed ||= !evaluation.vacuous;
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
if (evaluation.liveQuery && closedFrom(p)) {
|
|
500
|
+
const { iterations } = evaluation;
|
|
501
|
+
const closed = iterations === undefined ? undefined : { eligible: 0, entered: 0, complete: iterations.complete, late: evaluation.closed.count };
|
|
502
|
+
phases.push(progress(phase.name, 'complete', false, closed));
|
|
503
|
+
late.push(...evaluation.closed.late);
|
|
504
|
+
progressed = true;
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
if (phase.optional && closedFrom(p)) {
|
|
508
|
+
phases.push({ phase: phase.name, state: 'skipped' });
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
current = true;
|
|
512
|
+
phases.push(progress(phase.name, 'current', false, evaluation.iterations));
|
|
513
|
+
late.push(...evaluation.late);
|
|
514
|
+
frontier = [...evaluation.frontier];
|
|
515
|
+
diagnostics = [...evaluation.diagnostics];
|
|
516
|
+
// An optional current phase is reported together with the phases up to the next non-optional one (§4.10).
|
|
517
|
+
for (let q = p + 1; phase.optional && q < workflow.phases.length; q += 1) {
|
|
518
|
+
const later = evaluated(q);
|
|
519
|
+
if (later.complete)
|
|
520
|
+
continue;
|
|
521
|
+
frontier.push(...later.frontier);
|
|
522
|
+
diagnostics.push(...later.diagnostics);
|
|
523
|
+
if (!workflow.phases[q].optional)
|
|
524
|
+
break;
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
return { complete: !current, progressed, phases, frontier, diagnostics, late };
|
|
528
|
+
}
|
|
529
|
+
/** Evaluate phase `p` on its own: a plain `include`, an `iterate_over` or a leaf step. */
|
|
530
|
+
evaluate(instanceId, startCommit, workflow, p, frames, exit, trail, scope, reentries) {
|
|
531
|
+
const phase = workflow.phases[p];
|
|
532
|
+
// The include resolves: the registry refuses one naming no loaded workflow (`spec-003` loader rows).
|
|
533
|
+
if (phase.include !== undefined && phase.iterate_over !== undefined) {
|
|
534
|
+
return this.iterate(instanceId, startCommit, workflow, p, frames, exit, trail, scope, reentries);
|
|
535
|
+
}
|
|
536
|
+
// A plain `include` (§4.5): the sub runs on the same element, from the state this phase starts in.
|
|
537
|
+
if (phase.include !== undefined) {
|
|
538
|
+
const sub = this.byName.get(phase.include);
|
|
539
|
+
const result = this.run(instanceId, startCommit, sub, frames, { boundType: sub.element ?? exit.boundType, state: exit.entry, instance: false }, trail, reentries);
|
|
540
|
+
return {
|
|
541
|
+
complete: result.complete,
|
|
542
|
+
vacuous: result.complete && !result.progressed,
|
|
543
|
+
liveQuery: false,
|
|
544
|
+
frontier: result.frontier,
|
|
545
|
+
diagnostics: result.diagnostics,
|
|
546
|
+
late: result.late,
|
|
547
|
+
closed: { late: [], count: 0 },
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
const leaf = this.leaf(instanceId, startCommit, workflow, p, frames, exit, trail, scope, reentries);
|
|
551
|
+
const { kinds, missing } = leaf.step.evidence;
|
|
552
|
+
// A phase with `approval:` also needs no carried element to await it (§4.3, §5.1).
|
|
553
|
+
const complete = missing.length === 0 && (leaf.step.approval?.elements.length ?? 0) === 0;
|
|
554
|
+
const selection = kinds.includes('selection');
|
|
555
|
+
return {
|
|
556
|
+
complete,
|
|
557
|
+
// A selection that matches nothing, with no other evidence than an empty `created` (§4.7).
|
|
558
|
+
vacuous: complete && selection && leaf.step.created.length === 0 && kinds.every((kind) => kind === 'selection' || kind === 'created'),
|
|
559
|
+
liveQuery: selection,
|
|
560
|
+
frontier: [leaf.step],
|
|
561
|
+
diagnostics: leaf.diagnostics,
|
|
562
|
+
late: [],
|
|
563
|
+
closed: { late: leaf.selected, count: leaf.selected.length },
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
/** The `where` values of `phase`, resolved against `frames`, by key in byte order; `null` when a token has no value. */
|
|
567
|
+
wanted(phase, p, frames, report) {
|
|
568
|
+
const wanted = [];
|
|
569
|
+
let decidable = true;
|
|
570
|
+
for (const key of Object.keys(phase.where ?? {}).sort()) {
|
|
571
|
+
const raw = phase.where[key];
|
|
572
|
+
const values = (Array.isArray(raw) ? raw : [raw]).map((value) => {
|
|
573
|
+
if (typeof value !== 'string')
|
|
574
|
+
return String(value);
|
|
575
|
+
const resolved = resolveTokens(value, frames);
|
|
576
|
+
report(`phases[${p}].where.${key}`, resolved);
|
|
577
|
+
if (resolved.value === null)
|
|
578
|
+
decidable = false;
|
|
579
|
+
return resolved.value ?? value;
|
|
580
|
+
});
|
|
581
|
+
wanted.push([key, values]);
|
|
582
|
+
}
|
|
583
|
+
return decidable ? wanted : null;
|
|
584
|
+
}
|
|
585
|
+
/** The `W_UNRESOLVED_TOKEN` reporter of one step of `workflow`. */
|
|
586
|
+
reporter(workflow, stepName, diagnostics) {
|
|
587
|
+
const file = this.fileOf.get(workflow.name);
|
|
588
|
+
return (path, resolved) => {
|
|
589
|
+
for (const { token, reason } of resolved.unresolved) {
|
|
590
|
+
diagnostics.push({ code: exports.W_UNRESOLVED_TOKEN, severity: 'warning', file, path, message: `token '${token}' of ${stepName} has no value: ${reason}` });
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Comparator of the iteration order over `type` (`spec-017` §1.3): ascending by the `{n}` token of the
|
|
596
|
+
* type's `id_pattern` when it has one — an id that does not match the pattern after every one that
|
|
597
|
+
* does — else byte-wise ascending `id`.
|
|
598
|
+
*/
|
|
599
|
+
iterationOrder(type) {
|
|
600
|
+
const pattern = this.snapshot.memoryYaml.types[type].id_pattern;
|
|
601
|
+
const numeric = pattern !== undefined && [...pattern.matchAll(TOKEN_RE)].some((match) => (0, validation_1.isNumericToken)(match[1]));
|
|
602
|
+
const byId = (a, b) => compareText(a.id, b.id);
|
|
603
|
+
if (!numeric)
|
|
604
|
+
return byId;
|
|
605
|
+
const re = new RegExp(`^${(0, validation_1.patternToSource)(pattern, { captureNumeric: true })}$`);
|
|
606
|
+
const n = (element) => {
|
|
607
|
+
const match = re.exec(element.id);
|
|
608
|
+
return match === null ? Number.POSITIVE_INFINITY : Number(match[1]);
|
|
609
|
+
};
|
|
610
|
+
return (a, b) => {
|
|
611
|
+
const [x, y] = [n(a), n(b)];
|
|
612
|
+
return x === y ? byId(a, b) : x < y ? -1 : 1;
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* An `iterate_over` phase (§4.6): the sub once per candidate, in iteration order. Over a Memory type the
|
|
617
|
+
* `status` key of `where` is the entry filter and the other keys the scope filter; archived elements are
|
|
618
|
+
* never candidates (§4.11). Over a collection every entry matching `where` is a candidate. A candidate is
|
|
619
|
+
* complete when the sub is, entered when one of its phases is complete other than vacuously, eligible
|
|
620
|
+
* when it matches the entry filter, otherwise ignored. A `where` token with no value leaves the phase a
|
|
621
|
+
* single unexpanded step, reported with the token.
|
|
622
|
+
*/
|
|
623
|
+
iterate(instanceId, startCommit, workflow, p, frames, exit, trail, scope, reentries) {
|
|
624
|
+
const phase = workflow.phases[p];
|
|
625
|
+
const over = phase.iterate_over;
|
|
626
|
+
const sub = this.byName.get(phase.include);
|
|
627
|
+
const whereDiagnostics = [];
|
|
628
|
+
const wanted = this.wanted(phase, p, frames, this.reporter(workflow, `${workflow.name}.${phase.name}`, whereDiagnostics));
|
|
629
|
+
if (wanted === null) {
|
|
630
|
+
const leaf = this.leaf(instanceId, startCommit, workflow, p, frames, exit, trail, scope, reentries);
|
|
631
|
+
return { complete: false, vacuous: false, liveQuery: true, frontier: [leaf.step], diagnostics: [...whereDiagnostics, ...leaf.diagnostics], late: [], closed: { late: [], count: 0 } };
|
|
632
|
+
}
|
|
633
|
+
// Each candidate: the frame it adds, the state its sub starts from, whether it passes the entry filter.
|
|
634
|
+
const candidates = [];
|
|
635
|
+
let label = over;
|
|
636
|
+
let entries = this.snapshot.collections?.get(over) ?? [];
|
|
637
|
+
if (over.startsWith(ELEMENT_COLLECTION)) {
|
|
638
|
+
const read = elementCollection(over, frames);
|
|
639
|
+
if ('reason' in read) {
|
|
640
|
+
// Undecided, like a `where` token with no value: one unexpanded step, reported.
|
|
641
|
+
whereDiagnostics.push({ code: exports.W_UNRESOLVED_TOKEN, severity: 'warning', file: this.fileOf.get(workflow.name), path: `phases[${p}].iterate_over`, message: read.reason });
|
|
642
|
+
const leaf = this.leaf(instanceId, startCommit, workflow, p, frames, exit, trail, scope, reentries);
|
|
643
|
+
return { complete: false, vacuous: false, liveQuery: true, frontier: [leaf.step], diagnostics: [...whereDiagnostics, ...leaf.diagnostics], late: [], closed: { late: [], count: 0 } };
|
|
644
|
+
}
|
|
645
|
+
label = read.label;
|
|
646
|
+
entries = read.entries;
|
|
647
|
+
}
|
|
648
|
+
if (COLLECTION_REF_RE.test(over)) {
|
|
649
|
+
for (const item of entries) {
|
|
650
|
+
// Every key exists: the registry refuses a collection with a key problem (`E_BINDING_COLLECTION_KEY`,
|
|
651
|
+
// `E_WORKFLOW_COLLECTION_UNRESOLVED`).
|
|
652
|
+
const key = (0, bindings_1.collectionEntryKey)(item);
|
|
653
|
+
const fields = typeof item === 'object' && item !== null ? item : {};
|
|
654
|
+
if (!wanted.every(([field, values]) => matches(fields[field], values)))
|
|
655
|
+
continue;
|
|
656
|
+
candidates.push({ frame: { kind: 'item', collection: label, key, entry: item }, start: { boundType: null, state: null, instance: false }, entry: true, ref: null });
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
else {
|
|
660
|
+
const status = wanted.find(([key]) => key === 'status')?.[1];
|
|
661
|
+
const scopeFilter = wanted.filter(([key]) => key !== 'status');
|
|
662
|
+
const start = { boundType: sub.element ?? over, state: (0, workflow_exit_state_1.iterationStartState)(this.snapshot.memoryYaml, over, phase.where), instance: false };
|
|
663
|
+
this.elements
|
|
664
|
+
.filter((element) => element.type === over && !(0, state_machine_1.isArchivedStatus)(element.status) && scopeFilter.every(([key, values]) => matches(element.frontmatter[key], values)))
|
|
665
|
+
.sort(this.iterationOrder(over))
|
|
666
|
+
.forEach((element) => {
|
|
667
|
+
candidates.push({
|
|
668
|
+
frame: { kind: 'element', type: over, element, pending: false },
|
|
669
|
+
start,
|
|
670
|
+
entry: status === undefined || status.includes(element.status),
|
|
671
|
+
ref: refOf(element),
|
|
672
|
+
});
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
const counts = { eligible: 0, entered: 0, complete: 0 };
|
|
676
|
+
const frontier = [];
|
|
677
|
+
const diagnostics = [];
|
|
678
|
+
const late = [];
|
|
679
|
+
const completeLate = [];
|
|
680
|
+
const open = [];
|
|
681
|
+
let openCount = 0;
|
|
682
|
+
for (const candidate of candidates) {
|
|
683
|
+
// No cutoff is handed to an iteration: each iterated element has its own re-entries, which its sub's
|
|
684
|
+
// run finds (§4.8; approver ruling 2026-10-09).
|
|
685
|
+
const result = this.run(instanceId, startCommit, sub, [...frames, candidate.frame], candidate.start, trail);
|
|
686
|
+
if (result.complete) {
|
|
687
|
+
counts.complete += 1;
|
|
688
|
+
completeLate.push(...result.late);
|
|
689
|
+
continue;
|
|
690
|
+
}
|
|
691
|
+
if (result.progressed)
|
|
692
|
+
counts.entered += 1;
|
|
693
|
+
else if (candidate.entry)
|
|
694
|
+
counts.eligible += 1;
|
|
695
|
+
else
|
|
696
|
+
continue;
|
|
697
|
+
openCount += 1;
|
|
698
|
+
if (candidate.ref !== null)
|
|
699
|
+
open.push(candidate.ref);
|
|
700
|
+
frontier.push(...result.frontier);
|
|
701
|
+
diagnostics.push(...result.diagnostics);
|
|
702
|
+
late.push(...result.late);
|
|
703
|
+
}
|
|
704
|
+
const counted = counts.eligible + counts.entered + counts.complete;
|
|
705
|
+
return {
|
|
706
|
+
complete: openCount === 0,
|
|
707
|
+
vacuous: counted === 0,
|
|
708
|
+
liveQuery: true,
|
|
709
|
+
frontier,
|
|
710
|
+
diagnostics,
|
|
711
|
+
iterations: { ...counts, late: 0, ...(counted === 0 ? { note: exports.NO_ITERATION_NOTE } : {}) },
|
|
712
|
+
late: [...completeLate, ...late],
|
|
713
|
+
closed: { late: [...completeLate, ...open], count: openCount },
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
/** Evaluate one phase as a leaf step: its evidence, its `produces`, its unresolved tokens, what its selection matches. */
|
|
717
|
+
leaf(instanceId, startCommit, workflow, p, frames, exit, trail, scope, reentries) {
|
|
718
|
+
const phase = workflow.phases[p];
|
|
719
|
+
const stepName = `${workflow.name}.${phase.name}`;
|
|
720
|
+
const key = scope === null ? stepName : `${stepName}@${scopeKey(scope)}`;
|
|
721
|
+
const diagnostics = [];
|
|
722
|
+
const report = this.reporter(workflow, stepName, diagnostics);
|
|
723
|
+
const last = frames[frames.length - 1];
|
|
724
|
+
const frame = last?.kind === 'element' ? last : undefined;
|
|
725
|
+
const kinds = [];
|
|
726
|
+
const missing = [];
|
|
727
|
+
let selected = [];
|
|
728
|
+
// An `iterate_over` phase whose `where` cannot be resolved stays one unexpanded step (§4.6).
|
|
729
|
+
if (phase.iterate_over !== undefined) {
|
|
730
|
+
kinds.push('include');
|
|
731
|
+
missing.push('include');
|
|
732
|
+
}
|
|
733
|
+
// state — the bound element at or after the phase's exit state (§4.4), set by a commit newer than every
|
|
734
|
+
// re-entry that reaches this phase (§4.8).
|
|
735
|
+
if (declaresState(phase, exit.boundType)) {
|
|
736
|
+
kinds.push('state');
|
|
737
|
+
const element = frame?.element ?? null;
|
|
738
|
+
const satisfied = element !== null &&
|
|
739
|
+
exit.exit !== null &&
|
|
740
|
+
atOrAfter((0, state_machine_1.resolveStateMachine)(this.snapshot.memoryYaml, element.type), element.status, exit.exit) &&
|
|
741
|
+
this.newerThan(this.stateCommit(element), reentries);
|
|
742
|
+
if (!satisfied)
|
|
743
|
+
missing.push('state');
|
|
744
|
+
}
|
|
745
|
+
// What the step created (§4.8 linkage). A self-bound instance's steps had no scope before its element
|
|
746
|
+
// existed, so a linkage written then names the unscoped key.
|
|
747
|
+
const created = this.createdBy(instanceId, startCommit, frame?.selfBound ? [key, stepName] : [key]);
|
|
748
|
+
// produces — every string entry, resolved, names a committed path; implicit owners are shown only. A
|
|
749
|
+
// `{ type: T, path }` entry resolves against each element of `T` the step created and belongs to `created`.
|
|
750
|
+
const produces = [];
|
|
751
|
+
let producesEvidence = false;
|
|
752
|
+
let producesMissing = false;
|
|
753
|
+
let createdPathsMissing = false;
|
|
754
|
+
(phase.produces ?? []).forEach((entry, k) => {
|
|
755
|
+
if (typeof entry !== 'string') {
|
|
756
|
+
const owned = created.filter((element) => element.type === entry.type);
|
|
757
|
+
const resolved = [];
|
|
758
|
+
for (const element of owned) {
|
|
759
|
+
const result = resolveTokens(entry.path, [...frames, { kind: 'element', type: entry.type, element, pending: false }]);
|
|
760
|
+
report(`phases[${p}].produces[${k}].path`, result);
|
|
761
|
+
if (result.value !== null)
|
|
762
|
+
resolved.push(result.value);
|
|
763
|
+
}
|
|
764
|
+
const exists = owned.length > 0 && resolved.length === owned.length && resolved.every((path) => this.exists(path));
|
|
765
|
+
if (!exists)
|
|
766
|
+
createdPathsMissing = true;
|
|
767
|
+
produces.push({ pattern: entry.path, owner: entry.type, resolved, exists, evidence: false });
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
const resolved = resolveTokens(entry, frames);
|
|
771
|
+
report(`phases[${p}].produces[${k}]`, resolved);
|
|
772
|
+
const implicit = (0, workflow_diagnostics_1.isImplicitOwnerProduces)(workflow, p, entry);
|
|
773
|
+
const exists = resolved.value !== null && this.exists(resolved.value);
|
|
774
|
+
produces.push({
|
|
775
|
+
pattern: entry,
|
|
776
|
+
owner: implicit ? null : (frame?.type ?? null),
|
|
777
|
+
resolved: resolved.value === null ? [] : [resolved.value],
|
|
778
|
+
exists,
|
|
779
|
+
evidence: !implicit,
|
|
780
|
+
});
|
|
781
|
+
if (implicit)
|
|
782
|
+
return;
|
|
783
|
+
producesEvidence = true;
|
|
784
|
+
if (!exists)
|
|
785
|
+
producesMissing = true;
|
|
786
|
+
});
|
|
787
|
+
// created — for each type the phase adds, the step created at least one element, each at or after the
|
|
788
|
+
// state the phase leaves it in (§4.4), with each `{ type, path }` entry committed for it (§4.3). A step
|
|
789
|
+
// that created nothing leaves the kind to its other evidence, or to a record (below).
|
|
790
|
+
if ((phase.actions ?? []).some((action) => (0, bindings_1.tokenName)(action) === 'memory.add')) {
|
|
791
|
+
kinds.push('created');
|
|
792
|
+
if (created.length > 0 && (createdPathsMissing || !this.createdReachTargets(exit, created)))
|
|
793
|
+
missing.push('created');
|
|
794
|
+
}
|
|
795
|
+
if (producesEvidence) {
|
|
796
|
+
kinds.push('produces');
|
|
797
|
+
if (producesMissing)
|
|
798
|
+
missing.push('produces');
|
|
799
|
+
}
|
|
800
|
+
// selection — no Memory document at HEAD matches the phase's `where`; an archived one never does (§4.11).
|
|
801
|
+
if (phase.where !== undefined && phase.iterate_over === undefined) {
|
|
802
|
+
kinds.push('selection');
|
|
803
|
+
const wanted = this.wanted(phase, p, frames, report);
|
|
804
|
+
if (wanted !== null) {
|
|
805
|
+
selected = this.elements
|
|
806
|
+
.filter((element) => !(0, state_machine_1.isArchivedStatus)(element.status) && wanted.every(([field, values]) => matches(element.frontmatter[field], values)))
|
|
807
|
+
.map(refOf);
|
|
808
|
+
}
|
|
809
|
+
if (wanted === null || selected.length > 0)
|
|
810
|
+
missing.push('selection');
|
|
811
|
+
}
|
|
812
|
+
// awaits — evaluated from v1.0 (P4.12); the step completes by a record (§5.4).
|
|
813
|
+
if (phase.awaits !== undefined)
|
|
814
|
+
kinds.push('awaits');
|
|
815
|
+
// record — a checkpoint, an `awaits` (always, §4.3 `awaits` row, §5.4), a `created` step that created nothing
|
|
816
|
+
// and has no other evidence (§4.3), or an approval whose phase holds no gate: `workflow finalize` records it (§5.1).
|
|
817
|
+
const approvalRecord = phase.approval !== undefined && exit.held.length === 0;
|
|
818
|
+
const recordNeeded = phase.awaits !== undefined || approvalRecord || (created.length === 0 && !kinds.some((kind) => kind !== 'created'));
|
|
819
|
+
if (recordNeeded) {
|
|
820
|
+
kinds.push('record');
|
|
821
|
+
if (!this.hasRecord(instanceId, startCommit, stepName, scope, frame?.selfBound === true, reentries))
|
|
822
|
+
missing.push('record');
|
|
823
|
+
}
|
|
824
|
+
const actions = this.actionsOf(instanceId, startCommit, workflow, p, frames, exit, scope, created, selected, report);
|
|
825
|
+
const reentry = newest(reentries);
|
|
826
|
+
return {
|
|
827
|
+
step: {
|
|
828
|
+
key,
|
|
829
|
+
instance: instanceId,
|
|
830
|
+
trail,
|
|
831
|
+
workflow: workflow.name,
|
|
832
|
+
phase: phase.name,
|
|
833
|
+
scope,
|
|
834
|
+
role: phase.role ?? null,
|
|
835
|
+
optional: phase.optional,
|
|
836
|
+
produces,
|
|
837
|
+
created: created.map(refOf),
|
|
838
|
+
evidence: { kinds, missing, finalizable: missing.length === 1 && missing[0] === 'record' },
|
|
839
|
+
reentered: reentries.length > 0,
|
|
840
|
+
reentryCommit: reentry?.commit ?? null,
|
|
841
|
+
reentryFallback: reentry?.fallback ?? null,
|
|
842
|
+
approval: phase.approval === undefined ? null : { elements: awaitingElements(actions, exit), recordNeeded: approvalRecord },
|
|
843
|
+
actions,
|
|
844
|
+
},
|
|
845
|
+
diagnostics,
|
|
846
|
+
selected,
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
/**
|
|
850
|
+
* The actions of phase `p`, resolved (`spec-017` §4.1, §4.2; task-216): each token's text with its `{…}`
|
|
851
|
+
* substituted against `frames`, the tokens with no value (reported `W_UNRESOLVED_TOKEN` at
|
|
852
|
+
* `phases[p].actions[k]`), and what it acts on — the targets of §4.2's table, read at `HEAD`. A `{T.<field>}`
|
|
853
|
+
* whose `T` the phase's selection selects and no enclosing scope binds resolves per selected element when
|
|
854
|
+
* the action runs (§4.1), so it is kept as written and is not unresolved.
|
|
855
|
+
*/
|
|
856
|
+
actionsOf(instanceId, startCommit, workflow, p, frames, exit, scope, created, selected, report) {
|
|
857
|
+
const phase = workflow.phases[p];
|
|
858
|
+
const selectedTypes = phase.where !== undefined && phase.iterate_over === undefined ? asStrings(phase.where['type']) : [];
|
|
859
|
+
const last = frames[frames.length - 1];
|
|
860
|
+
const bound = last?.kind === 'element' ? last.element : null;
|
|
861
|
+
const sorted = (elements) => [...elements].sort(compareRefs);
|
|
862
|
+
let addedType = null;
|
|
863
|
+
return (phase.actions ?? []).map((token, a) => {
|
|
864
|
+
const resolved = resolveTokens(token, frames);
|
|
865
|
+
const unresolved = resolved.unresolved.filter(({ token: name, reason }) => !(reason === 'no element in scope' && selectedTypes.includes(name.slice(1, -1).split('.')[0])));
|
|
866
|
+
report(`phases[${p}].actions[${a}]`, { ...resolved, unresolved });
|
|
867
|
+
// Each token with a value substituted; a pending or unresolved one (no value) kept as written.
|
|
868
|
+
const text = token.replace(TOKEN_RE, (whole) => resolveTokens(whole, frames).value ?? whole);
|
|
869
|
+
// The bound element's state before this action: the static state (§4.4) once an earlier action of the phase
|
|
870
|
+
// moved it; until then its status at `HEAD`, since the static start of an instance is its type's first state.
|
|
871
|
+
// `actionStates` holds one entry per action (`workflow-exit-state.ts`).
|
|
872
|
+
const statics = exit.actionStates;
|
|
873
|
+
const boundFrom = bound !== null && statics[a] === statics[0] ? bound.status : statics[a];
|
|
874
|
+
const base = { token, text, unresolved: unresolved.map((entry) => entry.token), boundFrom };
|
|
875
|
+
const target = this.actionTarget(instanceId, startCommit, workflow, p, frames, exit, scope, created, selected, token, boundFrom, addedType);
|
|
876
|
+
if ((0, bindings_1.tokenName)(token) === 'memory.add')
|
|
877
|
+
addedType = (0, bindings_1.memoryAddType)(token);
|
|
878
|
+
return { ...base, ...target, targets: sorted(target.targets) };
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
/**
|
|
882
|
+
* What one action of phase `p` acts on (`spec-017` §4.2's table): `created` after (or at) a `memory.add` of
|
|
883
|
+
* the phase, else the phase's selection, else the bound element, for the untyped verbs; for `<T>.set_state`
|
|
884
|
+
* the bound element of `T`, else the elements of `T` an earlier step of the pass created (`run`), else the
|
|
885
|
+
* selection of `T`; for `<T>.sync_state(for_each: <S>.<field>)` the elements of `T` that field of the
|
|
886
|
+
* nearest `S` names; otherwise nothing.
|
|
887
|
+
*/
|
|
888
|
+
actionTarget(instanceId, startCommit, workflow, p, frames, exit, scope, created, selected, token, boundFrom, addedType) {
|
|
889
|
+
const phase = workflow.phases[p];
|
|
890
|
+
const name = (0, bindings_1.tokenName)(token);
|
|
891
|
+
const last = frames[frames.length - 1];
|
|
892
|
+
const frame = last?.kind === 'element' ? last : undefined;
|
|
893
|
+
const boundRefs = frame?.element ? [refOf(frame.element)] : [];
|
|
894
|
+
const ofType = (elements, type) => elements.filter((element) => element.type === type).map(refOf);
|
|
895
|
+
const none = { target: 'none', targetType: null, targets: [] };
|
|
896
|
+
if (name === 'memory.add') {
|
|
897
|
+
const type = (0, bindings_1.memoryAddType)(token);
|
|
898
|
+
return { target: 'created', targetType: type, targets: ofType(created, type) };
|
|
899
|
+
}
|
|
900
|
+
if (UNTYPED_TARGET_VERBS.includes(name)) {
|
|
901
|
+
if (addedType !== null)
|
|
902
|
+
return { target: 'created', targetType: addedType, targets: ofType(created, addedType) };
|
|
903
|
+
if (phase.where !== undefined && phase.iterate_over === undefined)
|
|
904
|
+
return { target: 'selection', targetType: null, targets: selected };
|
|
905
|
+
return frame === undefined ? none : { target: 'bound', targetType: frame.type, targets: boundRefs };
|
|
906
|
+
}
|
|
907
|
+
const typed = (0, bindings_1.typedStateType)(token);
|
|
908
|
+
if (typed === null)
|
|
909
|
+
return none;
|
|
910
|
+
if (name.endsWith('.set_state')) {
|
|
911
|
+
if (frame?.type === typed)
|
|
912
|
+
return { target: 'bound', targetType: typed, targets: boundRefs };
|
|
913
|
+
if (exit.run.some((element) => element.type === typed)) {
|
|
914
|
+
// The elements of `T` an earlier step of the same pass created (§4.2): the earlier phases' keys on this scope.
|
|
915
|
+
const suffix = scope === null ? '' : `@${scopeKey(scope)}`;
|
|
916
|
+
const earlier = workflow.phases.slice(0, p).flatMap(({ name: earlierName }) => {
|
|
917
|
+
const stepName = `${workflow.name}.${earlierName}`;
|
|
918
|
+
return frame?.selfBound ? [stepName + suffix, stepName] : [stepName + suffix];
|
|
919
|
+
});
|
|
920
|
+
return { target: 'run', targetType: typed, targets: ofType(this.createdBy(instanceId, startCommit, earlier), typed) };
|
|
921
|
+
}
|
|
922
|
+
const selection = phase.where !== undefined && phase.iterate_over === undefined && asStrings(phase.where['type']).includes(typed);
|
|
923
|
+
return { target: selection ? 'selection' : 'none', targetType: typed, targets: selection ? selected.filter((element) => element.type === typed) : [] };
|
|
924
|
+
}
|
|
925
|
+
// `<T>.sync_state(for_each: <S>.<field>)`: the elements of `T` the field of the nearest `S` names (§4.2).
|
|
926
|
+
const named = /for_each\s*:\s*["']?([a-z][a-z0-9-]*)\.([A-Za-z0-9_-]+)/.exec(token);
|
|
927
|
+
if (named === null)
|
|
928
|
+
return { target: 'run', targetType: typed, targets: [] };
|
|
929
|
+
const sourceType = named[1];
|
|
930
|
+
const sourceFrame = [...frames].reverse().find((candidate) => candidate.kind === 'element' && candidate.type === sourceType);
|
|
931
|
+
const source = sourceFrame?.element ?? null;
|
|
932
|
+
const ids = asStrings(source?.frontmatter[named[2]]);
|
|
933
|
+
const targets = ids.map((id) => this.find(typed, id)).filter((element) => element !== null);
|
|
934
|
+
const state = sourceFrame === frame ? boundFrom : (source?.status ?? null);
|
|
935
|
+
const peers = targets.map((target) => ({
|
|
936
|
+
id: target.id,
|
|
937
|
+
sources: this.elements
|
|
938
|
+
.filter((other) => other.type === sourceType && other.id !== source?.id && !(0, state_machine_1.isArchivedStatus)(other.status) && asStrings(other.frontmatter[named[2]]).includes(target.id))
|
|
939
|
+
.map(refOf)
|
|
940
|
+
.sort(compareRefs),
|
|
941
|
+
}));
|
|
942
|
+
return { target: 'run', targetType: typed, targets: targets.map(refOf), source: { type: sourceType, state, peers } };
|
|
943
|
+
}
|
|
944
|
+
/**
|
|
945
|
+
* Whether, for each type the phase adds (`exit.created`, its first `memory.add` of that type), the step
|
|
946
|
+
* created an element of it and each such element is at or after the state the phase leaves it in (§4.3,
|
|
947
|
+
* §4.4). An undetermined target state is never reached.
|
|
948
|
+
*/
|
|
949
|
+
createdReachTargets(exit, created) {
|
|
950
|
+
const targets = new Map();
|
|
951
|
+
for (const added of exit.created)
|
|
952
|
+
if (!targets.has(added.type))
|
|
953
|
+
targets.set(added.type, added.state);
|
|
954
|
+
for (const [type, target] of targets) {
|
|
955
|
+
const owned = created.filter((element) => element.type === type);
|
|
956
|
+
if (owned.length === 0 || target === null)
|
|
957
|
+
return false;
|
|
958
|
+
const machine = (0, state_machine_1.resolveStateMachine)(this.snapshot.memoryYaml, type);
|
|
959
|
+
if (!owned.every((element) => atOrAfter(machine, element.status, target)))
|
|
960
|
+
return false;
|
|
961
|
+
}
|
|
962
|
+
return true;
|
|
963
|
+
}
|
|
964
|
+
/**
|
|
965
|
+
* Whether the instance's walk holds a record of step `stepName` on `scope` (§4.8 records) — its
|
|
966
|
+
* `WingFoil-Element` or `WingFoil-Item` — newer than every re-entry that reaches the step (a descendant
|
|
967
|
+
* of each). A self-bound scope also accepts a record with no element, written before the element existed.
|
|
968
|
+
*/
|
|
969
|
+
hasRecord(instanceId, startCommit, stepName, scope, selfBound, reentries) {
|
|
970
|
+
const element = scope !== null && 'element' in scope ? scopeKey(scope) : null;
|
|
971
|
+
const item = scope !== null && 'item' in scope ? scopeKey(scope) : null;
|
|
972
|
+
return this.history(startCommit).records.some((record) => record.phase === stepName &&
|
|
973
|
+
record.instance === instanceId &&
|
|
974
|
+
(record.element === element || (selfBound && record.element === null)) &&
|
|
975
|
+
record.item === item &&
|
|
976
|
+
this.newerThan(record.commit, reentries));
|
|
977
|
+
}
|
|
978
|
+
/** The frames an instance of `workflow` starts with, its bound element and its context (§3.4, §3.5). */
|
|
979
|
+
bind(workflow, plan, startCommit) {
|
|
980
|
+
const id = scalarText(plan.frontmatter['element']);
|
|
981
|
+
if (workflow.element !== undefined) {
|
|
982
|
+
const element = id === null ? null : this.find(workflow.element, id);
|
|
983
|
+
return {
|
|
984
|
+
frames: [{ kind: 'element', type: workflow.element, element, pending: false }],
|
|
985
|
+
start: { boundType: workflow.element, state: this.firstState(workflow.element), instance: true },
|
|
986
|
+
element,
|
|
987
|
+
context: null,
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
const contextElement = id === null ? null : this.findAny(id);
|
|
991
|
+
const context = contextElement === null ? null : refOf(contextElement);
|
|
992
|
+
const creating = (0, workflow_diagnostics_1.creatingPhaseIndex)(workflow);
|
|
993
|
+
// The creating phase is the one holding the first `memory.add`, so it has actions; a `memory.add` with
|
|
994
|
+
// no `type:` names no type, and the instance then has no frame.
|
|
995
|
+
const type = creating === -1 ? null : (workflow.phases[creating].actions.map(bindings_1.memoryAddType).find((t) => t !== null) ?? null);
|
|
996
|
+
const start = { boundType: null, state: null, instance: true };
|
|
997
|
+
if (type === null)
|
|
998
|
+
return { frames: [], start, element: null, context };
|
|
999
|
+
// Self-creating (§3.4): bound to the element of `type` its creating step created with the oldest add
|
|
1000
|
+
// commit (ties by ascending id); until then unbound, its tokens pending.
|
|
1001
|
+
const step = `${workflow.name}.${workflow.phases[creating].name}`;
|
|
1002
|
+
const bound = this.history(startCommit)
|
|
1003
|
+
.links.filter((link) => link.instance === plan.id && link.step === step && link.type === type)
|
|
1004
|
+
.sort((a, b) => b.position - a.position || compareText(a.id, b.id))
|
|
1005
|
+
.map((link) => this.find(link.type, link.id))
|
|
1006
|
+
.find((element) => element !== null) ?? null;
|
|
1007
|
+
if (bound === null)
|
|
1008
|
+
return { frames: [{ kind: 'element', type, element: null, pending: true }], start, element: null, context };
|
|
1009
|
+
return { frames: [{ kind: 'element', type, element: bound, pending: false, selfBound: true }], start, element: bound, context };
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
/** A phase's progress entry, with `vacuous` and `iterations` only where they apply. */
|
|
1013
|
+
function progress(phase, state, vacuous, iterations) {
|
|
1014
|
+
return { phase, state, ...(vacuous ? { vacuous: true } : {}), ...(iterations !== undefined ? { iterations } : {}) };
|
|
1015
|
+
}
|
|
1016
|
+
// --- The deduction -----------------------------------------------------------------------------------
|
|
1017
|
+
/**
|
|
1018
|
+
* Deduce every open workflow instance at the snapshot's commit (`spec-017` §3–§4): its bound element,
|
|
1019
|
+
* its top-level phase progress and its frontier, plus the ordered diagnostics of §1.3 — the registry's,
|
|
1020
|
+
* then `W_UNCOMMITTED_INPUTS`, then `W_MEMORY_*` by path, then `W_INSTANCE_WORKFLOW_UNKNOWN` in
|
|
1021
|
+
* instance order, then the step-attached `W_UNRESOLVED_TOKEN` in frontier order. Never throws on a
|
|
1022
|
+
* Memory document it cannot use: it leaves the document out and reports it (§1.4).
|
|
1023
|
+
*/
|
|
1024
|
+
function deduceWorkflowState(snapshot) {
|
|
1025
|
+
const { elements, diagnostics: memoryDiagnostics } = readElements(snapshot);
|
|
1026
|
+
const deducer = new Deducer(snapshot, elements);
|
|
1027
|
+
const open = deducer
|
|
1028
|
+
.plans()
|
|
1029
|
+
.filter((plan) => exports.OPEN_PLAN_STATUSES.includes(plan.status) && scalarText(plan.frontmatter['parent']) === null)
|
|
1030
|
+
.map((plan) => {
|
|
1031
|
+
const start = snapshot.starts.get(plan.path);
|
|
1032
|
+
return { plan, workflowName: scalarText(plan.frontmatter['workflow']) ?? '', start, position: start?.position ?? Number.MAX_SAFE_INTEGER };
|
|
1033
|
+
})
|
|
1034
|
+
.filter(({ workflowName }) => {
|
|
1035
|
+
const workflow = deducer.workflow(workflowName);
|
|
1036
|
+
return workflow === undefined || (0, schema_1.workflowFacts)(workflow).startable;
|
|
1037
|
+
})
|
|
1038
|
+
.sort((a, b) => a.position - b.position || compareText(a.plan.id, b.plan.id));
|
|
1039
|
+
const unknownDiagnostics = [];
|
|
1040
|
+
const stepDiagnostics = [];
|
|
1041
|
+
const instances = open.map(({ plan, workflowName, start }, index) => {
|
|
1042
|
+
const workflow = deducer.workflow(workflowName);
|
|
1043
|
+
const startCommit = start?.commit ?? '';
|
|
1044
|
+
const base = {
|
|
1045
|
+
id: plan.id,
|
|
1046
|
+
workflow: workflowName,
|
|
1047
|
+
created: deducer.createdBy(plan.id, startCommit, null).map(refOf),
|
|
1048
|
+
planStatus: plan.status,
|
|
1049
|
+
startCommit,
|
|
1050
|
+
active: index === 0,
|
|
1051
|
+
abandoned: false,
|
|
1052
|
+
};
|
|
1053
|
+
if (workflow === undefined) {
|
|
1054
|
+
unknownDiagnostics.push({
|
|
1055
|
+
code: exports.W_INSTANCE_WORKFLOW_UNKNOWN,
|
|
1056
|
+
severity: 'warning',
|
|
1057
|
+
file: plan.path,
|
|
1058
|
+
path: 'workflow',
|
|
1059
|
+
message: `open plan ${plan.id} names workflow '${workflowName}', which the registry does not load`,
|
|
1060
|
+
});
|
|
1061
|
+
return { instance: { ...base, element: null, context: null }, complete: false, phases: [], frontier: [], late: [] };
|
|
1062
|
+
}
|
|
1063
|
+
const bound = deducer.bind(workflow, plan, startCommit);
|
|
1064
|
+
const element = bound.element === null ? null : refOf(bound.element);
|
|
1065
|
+
// An archived bound element abandons the instance: nothing is deduced for it (§4.11).
|
|
1066
|
+
if (bound.element !== null && (0, state_machine_1.isArchivedStatus)(bound.element.status)) {
|
|
1067
|
+
return { instance: { ...base, element, context: bound.context, abandoned: true }, complete: false, phases: [], frontier: [], late: [] };
|
|
1068
|
+
}
|
|
1069
|
+
const result = deducer.run(plan.id, startCommit, workflow, bound.frames, bound.start, []);
|
|
1070
|
+
stepDiagnostics.push(...result.diagnostics);
|
|
1071
|
+
const late = new Map(result.late.map((ref) => [`${ref.type}:${ref.id}`, ref]));
|
|
1072
|
+
return {
|
|
1073
|
+
instance: { ...base, element, context: bound.context },
|
|
1074
|
+
complete: result.complete,
|
|
1075
|
+
phases: result.phases,
|
|
1076
|
+
frontier: result.frontier,
|
|
1077
|
+
late: [...late.values()].sort(compareRefs),
|
|
1078
|
+
};
|
|
1079
|
+
});
|
|
1080
|
+
const uncommitted = snapshot.dirty.map(uncommittedInputDiagnostic);
|
|
1081
|
+
// The elements waiting at a gate (§5.1): `status` lists those no pending approval carries. Every element's type is
|
|
1082
|
+
// declared in `memory.yaml` (`readElements` leaves the others out), so the machine resolves. A gate that is also a
|
|
1083
|
+
// `waiting` state is left out: its forward edge is verb-less, so no approver acts on it — a workflow action advances
|
|
1084
|
+
// it (dl-123; `memory approve` refuses it).
|
|
1085
|
+
const gated = elements
|
|
1086
|
+
.filter((element) => {
|
|
1087
|
+
const machine = (0, state_machine_1.resolveStateMachine)(snapshot.memoryYaml, element.type);
|
|
1088
|
+
return Object.prototype.hasOwnProperty.call(machine.gates ?? {}, element.status) && !(machine.waiting ?? []).includes(element.status);
|
|
1089
|
+
})
|
|
1090
|
+
.map(refOf)
|
|
1091
|
+
.sort(compareRefs);
|
|
1092
|
+
return {
|
|
1093
|
+
baseline: { rev: 'HEAD', commit: snapshot.commit },
|
|
1094
|
+
active: instances[0]?.instance.id ?? null,
|
|
1095
|
+
instances,
|
|
1096
|
+
diagnostics: [...snapshot.registryDiagnostics, ...uncommitted, ...memoryDiagnostics, ...unknownDiagnostics, ...stepDiagnostics],
|
|
1097
|
+
gated,
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
/**
|
|
1101
|
+
* The open instance `ref` names (`spec-017` §3.3): a workflow name selects its most recently started
|
|
1102
|
+
* open instance, an instance id that instance; no `ref` selects the active instance. `null` when none.
|
|
1103
|
+
*/
|
|
1104
|
+
function resolveInstanceRef(deduction, ref) {
|
|
1105
|
+
if (ref === undefined)
|
|
1106
|
+
return deduction.instances[0] ?? null;
|
|
1107
|
+
return deduction.instances.find((entry) => entry.instance.workflow === ref) ?? deduction.instances.find((entry) => entry.instance.id === ref) ?? null;
|
|
1108
|
+
}
|
|
1109
|
+
//# sourceMappingURL=deduce.js.map
|