@kontourai/flow-agents 4.0.0 → 4.1.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/.github/workflows/ci.yml +8 -0
- package/CHANGELOG.md +27 -0
- package/CONTEXT.md +5 -0
- package/agents/tool-security-reviewer.json +1 -1
- package/build/src/cli/init.js +35 -3
- package/build/src/cli/kit.js +95 -2
- package/build/src/cli/pull-work-provider.js +7 -1
- package/build/src/cli/validate-workflow-artifacts.js +53 -0
- package/build/src/cli/workflow.js +5 -1
- package/build/src/flow-kit/provision.d.ts +23 -0
- package/build/src/flow-kit/provision.js +99 -0
- package/build/src/flow-kit/validate.d.ts +52 -0
- package/build/src/flow-kit/validate.js +214 -9
- package/build/src/runtime-adapters.js +1 -1
- package/context/contracts/execution-contract.md +30 -1
- package/context/contracts/review-contract.md +4 -0
- package/context/contracts/trigger-guards.md +73 -0
- package/context/settings/backlog-provider-settings.json +1 -1
- package/context/settings/workspace-backlog-provider-settings.example.json +1 -1
- package/dist/base/build/package.json +1 -1
- package/dist/base/build/src/cli/init.js +35 -3
- package/dist/base/build/src/cli/kit.js +95 -2
- package/dist/base/build/src/cli/pull-work-provider.js +7 -1
- package/dist/base/build/src/cli/validate-workflow-artifacts.js +53 -0
- package/dist/base/build/src/cli/workflow.js +5 -1
- package/dist/base/build/src/flow-kit/provision.d.ts +23 -0
- package/dist/base/build/src/flow-kit/provision.js +99 -0
- package/dist/base/build/src/flow-kit/validate.d.ts +52 -0
- package/dist/base/build/src/flow-kit/validate.js +214 -9
- package/dist/base/build/src/runtime-adapters.js +1 -1
- package/dist/base/context/contracts/execution-contract.md +30 -1
- package/dist/base/context/contracts/review-contract.md +4 -0
- package/dist/base/context/contracts/trigger-guards.md +73 -0
- package/dist/base/context/settings/backlog-provider-settings.json +1 -1
- package/dist/base/context/settings/workspace-backlog-provider-settings.example.json +1 -1
- package/dist/base/docs/adr/0023-kit-provisioning.md +47 -0
- package/dist/base/docs/adr/index.md +1 -0
- package/dist/base/docs/architecture-engine-and-kits.md +17 -0
- package/dist/base/docs/context-map.md +3 -0
- package/dist/base/docs/decisions/backlog-readiness-source.md +89 -0
- package/dist/base/docs/decisions/index.md +1 -0
- package/dist/base/docs/developer-architecture.md +3 -3
- package/dist/base/docs/flow-kit-repository-contract.md +34 -3
- package/dist/base/docs/kit-authoring-guide.md +1 -1
- package/dist/base/docs/veritas-integration.md +12 -0
- package/dist/base/evals/ci/run-baseline.sh +4 -0
- package/dist/base/evals/integration/test_kit_provisioning.sh +112 -0
- package/dist/base/evals/integration/test_pull_work_provider.sh +18 -0
- package/dist/base/evals/integration/test_veritas_governance_kit.sh +256 -0
- package/dist/base/evals/integration/test_workflow_artifacts.sh +257 -0
- package/dist/base/evals/static/test_package.sh +6 -0
- package/dist/base/evals/static/test_workflow_skills.sh +16 -0
- package/dist/base/install.sh +1 -1
- package/dist/base/kits/builder/flows/publish-learn.flow.json +9 -1
- package/dist/base/kits/builder/skills/execute-plan/SKILL.md +4 -3
- package/dist/base/kits/builder/skills/pull-work/SKILL.md +12 -0
- package/dist/base/kits/builder/skills/review-work/SKILL.md +11 -0
- package/dist/base/kits/veritas-governance/adapter/readiness-to-trust-bundle.mjs +19 -20
- package/dist/base/kits/veritas-governance/assets/starter-hooks/githooks/post-commit +13 -0
- package/dist/base/kits/veritas-governance/assets/starter-hooks/githooks/pre-push +13 -0
- package/dist/base/kits/veritas-governance/assets/starter-standards/veritas/GOVERNANCE.md +23 -0
- package/dist/base/kits/veritas-governance/assets/starter-standards/veritas/README.md +58 -0
- package/dist/base/kits/veritas-governance/assets/starter-standards/veritas/authority/default.authority-settings.json +30 -0
- package/dist/base/kits/veritas-governance/assets/starter-standards/veritas/repo-map.json +115 -0
- package/dist/base/kits/veritas-governance/assets/starter-standards/veritas/repo-standards/default.repo-standards.json +54 -0
- package/dist/base/kits/veritas-governance/assets/starter-standards/veritas.claims.json +179 -0
- package/dist/base/kits/veritas-governance/docs/README.md +141 -10
- package/dist/base/kits/veritas-governance/fixtures/standards-authoring/approved.trust-bundle.json +74 -0
- package/dist/base/kits/veritas-governance/fixtures/standards-authoring/not-approved.trust-bundle.json +74 -0
- package/dist/base/kits/veritas-governance/flows/standards-authoring.flow.json +35 -0
- package/dist/base/kits/veritas-governance/kit.json +65 -0
- package/dist/base/kits/veritas-governance/skills/consult-standards/SKILL.md +73 -0
- package/dist/base/kits/veritas-governance/skills/standards-authoring/SKILL.md +87 -0
- package/dist/base/schemas/backlog-provider-settings.schema.json +1 -1
- package/dist/base/schemas/workflow-waves.schema.json +347 -0
- package/dist/base/scripts/check-layer-boundary.mjs +98 -0
- package/dist/catalog.json +2 -0
- package/dist/claude-code/.claude/agents/tool-security-reviewer.md +2 -1
- package/dist/claude-code/.claude/skills/consult-standards/SKILL.md +73 -0
- package/dist/claude-code/.claude/skills/execute-plan/SKILL.md +4 -3
- package/dist/claude-code/.claude/skills/pull-work/SKILL.md +12 -0
- package/dist/claude-code/.claude/skills/review-work/SKILL.md +11 -0
- package/dist/claude-code/.claude/skills/standards-authoring/SKILL.md +87 -0
- package/dist/claude-code/build/package.json +1 -1
- package/dist/claude-code/build/src/cli/init.js +35 -3
- package/dist/claude-code/build/src/cli/kit.js +95 -2
- package/dist/claude-code/build/src/cli/pull-work-provider.js +7 -1
- package/dist/claude-code/build/src/cli/validate-workflow-artifacts.js +53 -0
- package/dist/claude-code/build/src/cli/workflow.js +5 -1
- package/dist/claude-code/build/src/flow-kit/provision.d.ts +23 -0
- package/dist/claude-code/build/src/flow-kit/provision.js +99 -0
- package/dist/claude-code/build/src/flow-kit/validate.d.ts +52 -0
- package/dist/claude-code/build/src/flow-kit/validate.js +214 -9
- package/dist/claude-code/build/src/runtime-adapters.js +1 -1
- package/dist/claude-code/context/contracts/execution-contract.md +30 -1
- package/dist/claude-code/context/contracts/review-contract.md +4 -0
- package/dist/claude-code/context/contracts/trigger-guards.md +73 -0
- package/dist/claude-code/context/settings/backlog-provider-settings.json +1 -1
- package/dist/claude-code/context/settings/workspace-backlog-provider-settings.example.json +1 -1
- package/dist/claude-code/docs/adr/0023-kit-provisioning.md +47 -0
- package/dist/claude-code/docs/adr/index.md +1 -0
- package/dist/claude-code/docs/architecture-engine-and-kits.md +17 -0
- package/dist/claude-code/docs/context-map.md +3 -0
- package/dist/claude-code/docs/decisions/backlog-readiness-source.md +89 -0
- package/dist/claude-code/docs/decisions/index.md +1 -0
- package/dist/claude-code/docs/developer-architecture.md +3 -3
- package/dist/claude-code/docs/flow-kit-repository-contract.md +34 -3
- package/dist/claude-code/docs/kit-authoring-guide.md +1 -1
- package/dist/claude-code/docs/veritas-integration.md +12 -0
- package/dist/claude-code/evals/ci/run-baseline.sh +4 -0
- package/dist/claude-code/evals/integration/test_kit_provisioning.sh +112 -0
- package/dist/claude-code/evals/integration/test_pull_work_provider.sh +18 -0
- package/dist/claude-code/evals/integration/test_veritas_governance_kit.sh +256 -0
- package/dist/claude-code/evals/integration/test_workflow_artifacts.sh +257 -0
- package/dist/claude-code/evals/static/test_package.sh +6 -0
- package/dist/claude-code/evals/static/test_workflow_skills.sh +16 -0
- package/dist/claude-code/install.sh +1 -1
- package/dist/claude-code/kits/builder/flows/publish-learn.flow.json +9 -1
- package/dist/claude-code/kits/builder/skills/execute-plan/SKILL.md +4 -3
- package/dist/claude-code/kits/builder/skills/pull-work/SKILL.md +12 -0
- package/dist/claude-code/kits/builder/skills/review-work/SKILL.md +11 -0
- package/dist/claude-code/kits/veritas-governance/adapter/readiness-to-trust-bundle.mjs +19 -20
- package/dist/claude-code/kits/veritas-governance/assets/starter-hooks/githooks/post-commit +13 -0
- package/dist/claude-code/kits/veritas-governance/assets/starter-hooks/githooks/pre-push +13 -0
- package/dist/claude-code/kits/veritas-governance/assets/starter-standards/veritas/GOVERNANCE.md +23 -0
- package/dist/claude-code/kits/veritas-governance/assets/starter-standards/veritas/README.md +58 -0
- package/dist/claude-code/kits/veritas-governance/assets/starter-standards/veritas/authority/default.authority-settings.json +30 -0
- package/dist/claude-code/kits/veritas-governance/assets/starter-standards/veritas/repo-map.json +115 -0
- package/dist/claude-code/kits/veritas-governance/assets/starter-standards/veritas/repo-standards/default.repo-standards.json +54 -0
- package/dist/claude-code/kits/veritas-governance/assets/starter-standards/veritas.claims.json +179 -0
- package/dist/claude-code/kits/veritas-governance/docs/README.md +141 -10
- package/dist/claude-code/kits/veritas-governance/fixtures/standards-authoring/approved.trust-bundle.json +74 -0
- package/dist/claude-code/kits/veritas-governance/fixtures/standards-authoring/not-approved.trust-bundle.json +74 -0
- package/dist/claude-code/kits/veritas-governance/flows/standards-authoring.flow.json +35 -0
- package/dist/claude-code/kits/veritas-governance/kit.json +65 -0
- package/dist/claude-code/kits/veritas-governance/skills/consult-standards/SKILL.md +73 -0
- package/dist/claude-code/kits/veritas-governance/skills/standards-authoring/SKILL.md +87 -0
- package/dist/claude-code/schemas/backlog-provider-settings.schema.json +1 -1
- package/dist/claude-code/schemas/workflow-waves.schema.json +347 -0
- package/dist/claude-code/scripts/check-layer-boundary.mjs +98 -0
- package/dist/codex/.agents/skills/builder-shape/context/contracts/execution-contract.md +30 -1
- package/dist/codex/.agents/skills/consult-standards/SKILL.md +73 -0
- package/dist/codex/.agents/skills/deliver/context/contracts/execution-contract.md +30 -1
- package/dist/codex/.agents/skills/design-probe/context/contracts/execution-contract.md +30 -1
- package/dist/codex/.agents/skills/execute-plan/SKILL.md +4 -3
- package/dist/codex/.agents/skills/execute-plan/context/contracts/execution-contract.md +30 -1
- package/dist/codex/.agents/skills/fix-bug/context/contracts/execution-contract.md +30 -1
- package/dist/codex/.agents/skills/idea-to-backlog/context/contracts/execution-contract.md +30 -1
- package/dist/codex/.agents/skills/plan-work/context/contracts/execution-contract.md +30 -1
- package/dist/codex/.agents/skills/pull-work/SKILL.md +12 -0
- package/dist/codex/.agents/skills/pull-work/context/contracts/execution-contract.md +30 -1
- package/dist/codex/.agents/skills/review-work/SKILL.md +11 -0
- package/dist/codex/.agents/skills/review-work/context/contracts/execution-contract.md +30 -1
- package/dist/codex/.agents/skills/standards-authoring/SKILL.md +87 -0
- package/dist/codex/.agents/skills/tdd-workflow/context/contracts/execution-contract.md +30 -1
- package/dist/codex/.agents/skills/verify-work/context/contracts/execution-contract.md +30 -1
- package/dist/codex/.codex/agents/tool-security-reviewer.toml +1 -1
- package/dist/codex/build/package.json +1 -1
- package/dist/codex/build/src/cli/init.js +35 -3
- package/dist/codex/build/src/cli/kit.js +95 -2
- package/dist/codex/build/src/cli/pull-work-provider.js +7 -1
- package/dist/codex/build/src/cli/validate-workflow-artifacts.js +53 -0
- package/dist/codex/build/src/cli/workflow.js +5 -1
- package/dist/codex/build/src/flow-kit/provision.d.ts +23 -0
- package/dist/codex/build/src/flow-kit/provision.js +99 -0
- package/dist/codex/build/src/flow-kit/validate.d.ts +52 -0
- package/dist/codex/build/src/flow-kit/validate.js +214 -9
- package/dist/codex/build/src/runtime-adapters.js +1 -1
- package/dist/codex/context/contracts/execution-contract.md +30 -1
- package/dist/codex/context/contracts/review-contract.md +4 -0
- package/dist/codex/context/contracts/trigger-guards.md +73 -0
- package/dist/codex/context/settings/backlog-provider-settings.json +1 -1
- package/dist/codex/context/settings/workspace-backlog-provider-settings.example.json +1 -1
- package/dist/codex/docs/adr/0023-kit-provisioning.md +47 -0
- package/dist/codex/docs/adr/index.md +1 -0
- package/dist/codex/docs/architecture-engine-and-kits.md +17 -0
- package/dist/codex/docs/context-map.md +3 -0
- package/dist/codex/docs/decisions/backlog-readiness-source.md +89 -0
- package/dist/codex/docs/decisions/index.md +1 -0
- package/dist/codex/docs/developer-architecture.md +3 -3
- package/dist/codex/docs/flow-kit-repository-contract.md +34 -3
- package/dist/codex/docs/kit-authoring-guide.md +1 -1
- package/dist/codex/docs/veritas-integration.md +12 -0
- package/dist/codex/evals/ci/run-baseline.sh +4 -0
- package/dist/codex/evals/integration/test_kit_provisioning.sh +112 -0
- package/dist/codex/evals/integration/test_pull_work_provider.sh +18 -0
- package/dist/codex/evals/integration/test_veritas_governance_kit.sh +256 -0
- package/dist/codex/evals/integration/test_workflow_artifacts.sh +257 -0
- package/dist/codex/evals/static/test_package.sh +6 -0
- package/dist/codex/evals/static/test_workflow_skills.sh +16 -0
- package/dist/codex/install.sh +1 -1
- package/dist/codex/kits/builder/flows/publish-learn.flow.json +9 -1
- package/dist/codex/kits/builder/skills/execute-plan/SKILL.md +4 -3
- package/dist/codex/kits/builder/skills/pull-work/SKILL.md +12 -0
- package/dist/codex/kits/builder/skills/review-work/SKILL.md +11 -0
- package/dist/codex/kits/veritas-governance/adapter/readiness-to-trust-bundle.mjs +19 -20
- package/dist/codex/kits/veritas-governance/assets/starter-hooks/githooks/post-commit +13 -0
- package/dist/codex/kits/veritas-governance/assets/starter-hooks/githooks/pre-push +13 -0
- package/dist/codex/kits/veritas-governance/assets/starter-standards/veritas/GOVERNANCE.md +23 -0
- package/dist/codex/kits/veritas-governance/assets/starter-standards/veritas/README.md +58 -0
- package/dist/codex/kits/veritas-governance/assets/starter-standards/veritas/authority/default.authority-settings.json +30 -0
- package/dist/codex/kits/veritas-governance/assets/starter-standards/veritas/repo-map.json +115 -0
- package/dist/codex/kits/veritas-governance/assets/starter-standards/veritas/repo-standards/default.repo-standards.json +54 -0
- package/dist/codex/kits/veritas-governance/assets/starter-standards/veritas.claims.json +179 -0
- package/dist/codex/kits/veritas-governance/docs/README.md +141 -10
- package/dist/codex/kits/veritas-governance/fixtures/standards-authoring/approved.trust-bundle.json +74 -0
- package/dist/codex/kits/veritas-governance/fixtures/standards-authoring/not-approved.trust-bundle.json +74 -0
- package/dist/codex/kits/veritas-governance/flows/standards-authoring.flow.json +35 -0
- package/dist/codex/kits/veritas-governance/kit.json +65 -0
- package/dist/codex/kits/veritas-governance/skills/consult-standards/SKILL.md +73 -0
- package/dist/codex/kits/veritas-governance/skills/standards-authoring/SKILL.md +87 -0
- package/dist/codex/schemas/backlog-provider-settings.schema.json +1 -1
- package/dist/codex/schemas/workflow-waves.schema.json +347 -0
- package/dist/codex/scripts/check-layer-boundary.mjs +98 -0
- package/dist/kiro/agents/tool-security-reviewer.json +1 -1
- package/dist/kiro/build/package.json +1 -1
- package/dist/kiro/build/src/cli/init.js +35 -3
- package/dist/kiro/build/src/cli/kit.js +95 -2
- package/dist/kiro/build/src/cli/pull-work-provider.js +7 -1
- package/dist/kiro/build/src/cli/validate-workflow-artifacts.js +53 -0
- package/dist/kiro/build/src/cli/workflow.js +5 -1
- package/dist/kiro/build/src/flow-kit/provision.d.ts +23 -0
- package/dist/kiro/build/src/flow-kit/provision.js +99 -0
- package/dist/kiro/build/src/flow-kit/validate.d.ts +52 -0
- package/dist/kiro/build/src/flow-kit/validate.js +214 -9
- package/dist/kiro/build/src/runtime-adapters.js +1 -1
- package/dist/kiro/context/contracts/execution-contract.md +30 -1
- package/dist/kiro/context/contracts/review-contract.md +4 -0
- package/dist/kiro/context/contracts/trigger-guards.md +73 -0
- package/dist/kiro/context/settings/backlog-provider-settings.json +1 -1
- package/dist/kiro/context/settings/workspace-backlog-provider-settings.example.json +1 -1
- package/dist/kiro/docs/adr/0023-kit-provisioning.md +47 -0
- package/dist/kiro/docs/adr/index.md +1 -0
- package/dist/kiro/docs/architecture-engine-and-kits.md +17 -0
- package/dist/kiro/docs/context-map.md +3 -0
- package/dist/kiro/docs/decisions/backlog-readiness-source.md +89 -0
- package/dist/kiro/docs/decisions/index.md +1 -0
- package/dist/kiro/docs/developer-architecture.md +3 -3
- package/dist/kiro/docs/flow-kit-repository-contract.md +34 -3
- package/dist/kiro/docs/kit-authoring-guide.md +1 -1
- package/dist/kiro/docs/veritas-integration.md +12 -0
- package/dist/kiro/evals/ci/run-baseline.sh +4 -0
- package/dist/kiro/evals/integration/test_kit_provisioning.sh +112 -0
- package/dist/kiro/evals/integration/test_pull_work_provider.sh +18 -0
- package/dist/kiro/evals/integration/test_veritas_governance_kit.sh +256 -0
- package/dist/kiro/evals/integration/test_workflow_artifacts.sh +257 -0
- package/dist/kiro/evals/static/test_package.sh +6 -0
- package/dist/kiro/evals/static/test_workflow_skills.sh +16 -0
- package/dist/kiro/install.sh +1 -1
- package/dist/kiro/kits/builder/flows/publish-learn.flow.json +9 -1
- package/dist/kiro/kits/builder/skills/execute-plan/SKILL.md +4 -3
- package/dist/kiro/kits/builder/skills/pull-work/SKILL.md +12 -0
- package/dist/kiro/kits/builder/skills/review-work/SKILL.md +11 -0
- package/dist/kiro/kits/veritas-governance/adapter/readiness-to-trust-bundle.mjs +19 -20
- package/dist/kiro/kits/veritas-governance/assets/starter-hooks/githooks/post-commit +13 -0
- package/dist/kiro/kits/veritas-governance/assets/starter-hooks/githooks/pre-push +13 -0
- package/dist/kiro/kits/veritas-governance/assets/starter-standards/veritas/GOVERNANCE.md +23 -0
- package/dist/kiro/kits/veritas-governance/assets/starter-standards/veritas/README.md +58 -0
- package/dist/kiro/kits/veritas-governance/assets/starter-standards/veritas/authority/default.authority-settings.json +30 -0
- package/dist/kiro/kits/veritas-governance/assets/starter-standards/veritas/repo-map.json +115 -0
- package/dist/kiro/kits/veritas-governance/assets/starter-standards/veritas/repo-standards/default.repo-standards.json +54 -0
- package/dist/kiro/kits/veritas-governance/assets/starter-standards/veritas.claims.json +179 -0
- package/dist/kiro/kits/veritas-governance/docs/README.md +141 -10
- package/dist/kiro/kits/veritas-governance/fixtures/standards-authoring/approved.trust-bundle.json +74 -0
- package/dist/kiro/kits/veritas-governance/fixtures/standards-authoring/not-approved.trust-bundle.json +74 -0
- package/dist/kiro/kits/veritas-governance/flows/standards-authoring.flow.json +35 -0
- package/dist/kiro/kits/veritas-governance/kit.json +65 -0
- package/dist/kiro/kits/veritas-governance/skills/consult-standards/SKILL.md +73 -0
- package/dist/kiro/kits/veritas-governance/skills/standards-authoring/SKILL.md +87 -0
- package/dist/kiro/schemas/backlog-provider-settings.schema.json +1 -1
- package/dist/kiro/schemas/workflow-waves.schema.json +347 -0
- package/dist/kiro/scripts/check-layer-boundary.mjs +98 -0
- package/dist/opencode/.opencode/agents/tool-security-reviewer.md +2 -1
- package/dist/opencode/.opencode/skills/consult-standards/SKILL.md +73 -0
- package/dist/opencode/.opencode/skills/execute-plan/SKILL.md +4 -3
- package/dist/opencode/.opencode/skills/pull-work/SKILL.md +12 -0
- package/dist/opencode/.opencode/skills/review-work/SKILL.md +11 -0
- package/dist/opencode/.opencode/skills/standards-authoring/SKILL.md +87 -0
- package/dist/opencode/build/package.json +1 -1
- package/dist/opencode/build/src/cli/init.js +35 -3
- package/dist/opencode/build/src/cli/kit.js +95 -2
- package/dist/opencode/build/src/cli/pull-work-provider.js +7 -1
- package/dist/opencode/build/src/cli/validate-workflow-artifacts.js +53 -0
- package/dist/opencode/build/src/cli/workflow.js +5 -1
- package/dist/opencode/build/src/flow-kit/provision.d.ts +23 -0
- package/dist/opencode/build/src/flow-kit/provision.js +99 -0
- package/dist/opencode/build/src/flow-kit/validate.d.ts +52 -0
- package/dist/opencode/build/src/flow-kit/validate.js +214 -9
- package/dist/opencode/build/src/runtime-adapters.js +1 -1
- package/dist/opencode/context/contracts/execution-contract.md +30 -1
- package/dist/opencode/context/contracts/review-contract.md +4 -0
- package/dist/opencode/context/contracts/trigger-guards.md +73 -0
- package/dist/opencode/context/settings/backlog-provider-settings.json +1 -1
- package/dist/opencode/context/settings/workspace-backlog-provider-settings.example.json +1 -1
- package/dist/opencode/docs/adr/0023-kit-provisioning.md +47 -0
- package/dist/opencode/docs/adr/index.md +1 -0
- package/dist/opencode/docs/architecture-engine-and-kits.md +17 -0
- package/dist/opencode/docs/context-map.md +3 -0
- package/dist/opencode/docs/decisions/backlog-readiness-source.md +89 -0
- package/dist/opencode/docs/decisions/index.md +1 -0
- package/dist/opencode/docs/developer-architecture.md +3 -3
- package/dist/opencode/docs/flow-kit-repository-contract.md +34 -3
- package/dist/opencode/docs/kit-authoring-guide.md +1 -1
- package/dist/opencode/docs/veritas-integration.md +12 -0
- package/dist/opencode/evals/ci/run-baseline.sh +4 -0
- package/dist/opencode/evals/integration/test_kit_provisioning.sh +112 -0
- package/dist/opencode/evals/integration/test_pull_work_provider.sh +18 -0
- package/dist/opencode/evals/integration/test_veritas_governance_kit.sh +256 -0
- package/dist/opencode/evals/integration/test_workflow_artifacts.sh +257 -0
- package/dist/opencode/evals/static/test_package.sh +6 -0
- package/dist/opencode/evals/static/test_workflow_skills.sh +16 -0
- package/dist/opencode/install.sh +1 -1
- package/dist/opencode/kits/builder/flows/publish-learn.flow.json +9 -1
- package/dist/opencode/kits/builder/skills/execute-plan/SKILL.md +4 -3
- package/dist/opencode/kits/builder/skills/pull-work/SKILL.md +12 -0
- package/dist/opencode/kits/builder/skills/review-work/SKILL.md +11 -0
- package/dist/opencode/kits/veritas-governance/adapter/readiness-to-trust-bundle.mjs +19 -20
- package/dist/opencode/kits/veritas-governance/assets/starter-hooks/githooks/post-commit +13 -0
- package/dist/opencode/kits/veritas-governance/assets/starter-hooks/githooks/pre-push +13 -0
- package/dist/opencode/kits/veritas-governance/assets/starter-standards/veritas/GOVERNANCE.md +23 -0
- package/dist/opencode/kits/veritas-governance/assets/starter-standards/veritas/README.md +58 -0
- package/dist/opencode/kits/veritas-governance/assets/starter-standards/veritas/authority/default.authority-settings.json +30 -0
- package/dist/opencode/kits/veritas-governance/assets/starter-standards/veritas/repo-map.json +115 -0
- package/dist/opencode/kits/veritas-governance/assets/starter-standards/veritas/repo-standards/default.repo-standards.json +54 -0
- package/dist/opencode/kits/veritas-governance/assets/starter-standards/veritas.claims.json +179 -0
- package/dist/opencode/kits/veritas-governance/docs/README.md +141 -10
- package/dist/opencode/kits/veritas-governance/fixtures/standards-authoring/approved.trust-bundle.json +74 -0
- package/dist/opencode/kits/veritas-governance/fixtures/standards-authoring/not-approved.trust-bundle.json +74 -0
- package/dist/opencode/kits/veritas-governance/flows/standards-authoring.flow.json +35 -0
- package/dist/opencode/kits/veritas-governance/kit.json +65 -0
- package/dist/opencode/kits/veritas-governance/skills/consult-standards/SKILL.md +73 -0
- package/dist/opencode/kits/veritas-governance/skills/standards-authoring/SKILL.md +87 -0
- package/dist/opencode/schemas/backlog-provider-settings.schema.json +1 -1
- package/dist/opencode/schemas/workflow-waves.schema.json +347 -0
- package/dist/opencode/scripts/check-layer-boundary.mjs +98 -0
- package/dist/pi/.pi/skills/consult-standards/SKILL.md +73 -0
- package/dist/pi/.pi/skills/execute-plan/SKILL.md +4 -3
- package/dist/pi/.pi/skills/pull-work/SKILL.md +12 -0
- package/dist/pi/.pi/skills/review-work/SKILL.md +11 -0
- package/dist/pi/.pi/skills/standards-authoring/SKILL.md +87 -0
- package/dist/pi/build/package.json +1 -1
- package/dist/pi/build/src/cli/init.js +35 -3
- package/dist/pi/build/src/cli/kit.js +95 -2
- package/dist/pi/build/src/cli/pull-work-provider.js +7 -1
- package/dist/pi/build/src/cli/validate-workflow-artifacts.js +53 -0
- package/dist/pi/build/src/cli/workflow.js +5 -1
- package/dist/pi/build/src/flow-kit/provision.d.ts +23 -0
- package/dist/pi/build/src/flow-kit/provision.js +99 -0
- package/dist/pi/build/src/flow-kit/validate.d.ts +52 -0
- package/dist/pi/build/src/flow-kit/validate.js +214 -9
- package/dist/pi/build/src/runtime-adapters.js +1 -1
- package/dist/pi/context/contracts/execution-contract.md +30 -1
- package/dist/pi/context/contracts/review-contract.md +4 -0
- package/dist/pi/context/contracts/trigger-guards.md +73 -0
- package/dist/pi/context/settings/backlog-provider-settings.json +1 -1
- package/dist/pi/context/settings/workspace-backlog-provider-settings.example.json +1 -1
- package/dist/pi/docs/adr/0023-kit-provisioning.md +47 -0
- package/dist/pi/docs/adr/index.md +1 -0
- package/dist/pi/docs/architecture-engine-and-kits.md +17 -0
- package/dist/pi/docs/context-map.md +3 -0
- package/dist/pi/docs/decisions/backlog-readiness-source.md +89 -0
- package/dist/pi/docs/decisions/index.md +1 -0
- package/dist/pi/docs/developer-architecture.md +3 -3
- package/dist/pi/docs/flow-kit-repository-contract.md +34 -3
- package/dist/pi/docs/kit-authoring-guide.md +1 -1
- package/dist/pi/docs/veritas-integration.md +12 -0
- package/dist/pi/evals/ci/run-baseline.sh +4 -0
- package/dist/pi/evals/integration/test_kit_provisioning.sh +112 -0
- package/dist/pi/evals/integration/test_pull_work_provider.sh +18 -0
- package/dist/pi/evals/integration/test_veritas_governance_kit.sh +256 -0
- package/dist/pi/evals/integration/test_workflow_artifacts.sh +257 -0
- package/dist/pi/evals/static/test_package.sh +6 -0
- package/dist/pi/evals/static/test_workflow_skills.sh +16 -0
- package/dist/pi/install.sh +1 -1
- package/dist/pi/kits/builder/flows/publish-learn.flow.json +9 -1
- package/dist/pi/kits/builder/skills/execute-plan/SKILL.md +4 -3
- package/dist/pi/kits/builder/skills/pull-work/SKILL.md +12 -0
- package/dist/pi/kits/builder/skills/review-work/SKILL.md +11 -0
- package/dist/pi/kits/veritas-governance/adapter/readiness-to-trust-bundle.mjs +19 -20
- package/dist/pi/kits/veritas-governance/assets/starter-hooks/githooks/post-commit +13 -0
- package/dist/pi/kits/veritas-governance/assets/starter-hooks/githooks/pre-push +13 -0
- package/dist/pi/kits/veritas-governance/assets/starter-standards/veritas/GOVERNANCE.md +23 -0
- package/dist/pi/kits/veritas-governance/assets/starter-standards/veritas/README.md +58 -0
- package/dist/pi/kits/veritas-governance/assets/starter-standards/veritas/authority/default.authority-settings.json +30 -0
- package/dist/pi/kits/veritas-governance/assets/starter-standards/veritas/repo-map.json +115 -0
- package/dist/pi/kits/veritas-governance/assets/starter-standards/veritas/repo-standards/default.repo-standards.json +54 -0
- package/dist/pi/kits/veritas-governance/assets/starter-standards/veritas.claims.json +179 -0
- package/dist/pi/kits/veritas-governance/docs/README.md +141 -10
- package/dist/pi/kits/veritas-governance/fixtures/standards-authoring/approved.trust-bundle.json +74 -0
- package/dist/pi/kits/veritas-governance/fixtures/standards-authoring/not-approved.trust-bundle.json +74 -0
- package/dist/pi/kits/veritas-governance/flows/standards-authoring.flow.json +35 -0
- package/dist/pi/kits/veritas-governance/kit.json +65 -0
- package/dist/pi/kits/veritas-governance/skills/consult-standards/SKILL.md +73 -0
- package/dist/pi/kits/veritas-governance/skills/standards-authoring/SKILL.md +87 -0
- package/dist/pi/schemas/backlog-provider-settings.schema.json +1 -1
- package/dist/pi/schemas/workflow-waves.schema.json +347 -0
- package/dist/pi/scripts/check-layer-boundary.mjs +98 -0
- package/docs/adr/0023-kit-provisioning.md +47 -0
- package/docs/adr/index.md +1 -0
- package/docs/architecture-engine-and-kits.md +17 -0
- package/docs/context-map.md +3 -0
- package/docs/decisions/backlog-readiness-source.md +89 -0
- package/docs/decisions/index.md +1 -0
- package/docs/developer-architecture.md +3 -3
- package/docs/flow-kit-repository-contract.md +34 -3
- package/docs/kit-authoring-guide.md +1 -1
- package/docs/veritas-integration.md +12 -0
- package/evals/ci/run-baseline.sh +4 -0
- package/evals/integration/test_kit_provisioning.sh +112 -0
- package/evals/integration/test_pull_work_provider.sh +18 -0
- package/evals/integration/test_veritas_governance_kit.sh +256 -0
- package/evals/integration/test_workflow_artifacts.sh +257 -0
- package/evals/static/test_package.sh +6 -0
- package/evals/static/test_workflow_skills.sh +16 -0
- package/kits/builder/flows/publish-learn.flow.json +9 -1
- package/kits/builder/skills/execute-plan/SKILL.md +4 -3
- package/kits/builder/skills/pull-work/SKILL.md +12 -0
- package/kits/builder/skills/review-work/SKILL.md +11 -0
- package/kits/veritas-governance/adapter/readiness-to-trust-bundle.mjs +19 -20
- package/kits/veritas-governance/assets/starter-hooks/githooks/post-commit +13 -0
- package/kits/veritas-governance/assets/starter-hooks/githooks/pre-push +13 -0
- package/kits/veritas-governance/assets/starter-standards/veritas/GOVERNANCE.md +23 -0
- package/kits/veritas-governance/assets/starter-standards/veritas/README.md +58 -0
- package/kits/veritas-governance/assets/starter-standards/veritas/authority/default.authority-settings.json +30 -0
- package/kits/veritas-governance/assets/starter-standards/veritas/repo-map.json +115 -0
- package/kits/veritas-governance/assets/starter-standards/veritas/repo-standards/default.repo-standards.json +54 -0
- package/kits/veritas-governance/assets/starter-standards/veritas.claims.json +179 -0
- package/kits/veritas-governance/docs/README.md +141 -10
- package/kits/veritas-governance/fixtures/standards-authoring/approved.trust-bundle.json +74 -0
- package/kits/veritas-governance/fixtures/standards-authoring/not-approved.trust-bundle.json +74 -0
- package/kits/veritas-governance/flows/standards-authoring.flow.json +35 -0
- package/kits/veritas-governance/kit.json +65 -0
- package/kits/veritas-governance/skills/consult-standards/SKILL.md +73 -0
- package/kits/veritas-governance/skills/standards-authoring/SKILL.md +87 -0
- package/package.json +2 -1
- package/schemas/backlog-provider-settings.schema.json +1 -1
- package/schemas/workflow-waves.schema.json +347 -0
- package/scripts/check-layer-boundary.mjs +98 -0
- package/src/cli/builder-flow-runtime.test.mjs +86 -0
- package/src/cli/flow-resolver-composition.test.mjs +20 -1
- package/src/cli/init.ts +31 -3
- package/src/cli/kit-metadata-security.test.mjs +163 -1
- package/src/cli/kit-provisioning.test.mjs +122 -0
- package/src/cli/kit.ts +83 -2
- package/src/cli/pull-work-provider.ts +7 -1
- package/src/cli/validate-waves.test.mjs +134 -0
- package/src/cli/validate-workflow-artifacts.ts +47 -0
- package/src/cli/workflow.ts +7 -1
- package/src/flow-kit/provision.ts +118 -0
- package/src/flow-kit/validate.ts +233 -10
- package/src/runtime-adapters.ts +1 -1
|
@@ -7,7 +7,7 @@ import { createRequire, syncBuiltinESMExports } from "node:module";
|
|
|
7
7
|
|
|
8
8
|
import { readKitInventory } from "../../build/src/runtime-adapters.js";
|
|
9
9
|
import { main as validateHookInfluence } from "../../build/src/cli/validate-hook-influence.js";
|
|
10
|
-
import { parseKitFlowStepActions, parseKitSkillRoles, validateKitRepository } from "../../build/src/flow-kit/validate.js";
|
|
10
|
+
import { parseKitAgentSpawnTriggers, parseKitFlowStepActions, parseKitSkillRoles, validateKitRepository, validateKitRepositoryDiagnostics } from "../../build/src/flow-kit/validate.js";
|
|
11
11
|
import { observeBuilderArtifactsForProgress } from "../../build/src/builder-gate-action-envelope.js";
|
|
12
12
|
|
|
13
13
|
const require = createRequire(import.meta.url);
|
|
@@ -908,3 +908,165 @@ test("workflowTriggersFor returns Knowledge's structured capture trigger", () =>
|
|
|
908
908
|
assert.match(trigger.steering, /Keep the session on `knowledge\.ingest`/);
|
|
909
909
|
assert.match(trigger.steering, /unsupported-runtime blocker/);
|
|
910
910
|
});
|
|
911
|
+
|
|
912
|
+
test("agent spawn trigger metadata accepts a fully guarded declaration without errors or warnings", () => {
|
|
913
|
+
const result = parseKitAgentSpawnTriggers({
|
|
914
|
+
agent_spawn_triggers: [
|
|
915
|
+
{
|
|
916
|
+
id: "on-check-failure",
|
|
917
|
+
description: "Escalates failing checks to a headless agent run.",
|
|
918
|
+
spawns_agent_runs: true,
|
|
919
|
+
guards: { dedup_key: "check-name+failure-signature", cooldown_seconds: 900, daily_cap: 20, max_concurrent: 1 },
|
|
920
|
+
},
|
|
921
|
+
{
|
|
922
|
+
id: "notify-only",
|
|
923
|
+
description: "Fires a notification; never spawns agent runs.",
|
|
924
|
+
spawns_agent_runs: false,
|
|
925
|
+
},
|
|
926
|
+
],
|
|
927
|
+
}, "fixture/kit.json");
|
|
928
|
+
|
|
929
|
+
assert.deepEqual(result.errors, []);
|
|
930
|
+
assert.deepEqual(result.warnings, []);
|
|
931
|
+
assert.equal(result.entries.length, 2);
|
|
932
|
+
assert.deepEqual(result.entries[0].guards, { dedup_key: "check-name+failure-signature", cooldown_seconds: 900, daily_cap: 20, max_concurrent: 1 });
|
|
933
|
+
assert.equal(result.entries[1].guards, undefined);
|
|
934
|
+
});
|
|
935
|
+
|
|
936
|
+
test("agent spawn trigger without guards warns (never errors) when it spawns agent runs", () => {
|
|
937
|
+
const missing = parseKitAgentSpawnTriggers({
|
|
938
|
+
agent_spawn_triggers: [
|
|
939
|
+
{ id: "on-schedule", description: "Scheduled automation spawning agent runs.", spawns_agent_runs: true },
|
|
940
|
+
],
|
|
941
|
+
}, "fixture/kit.json");
|
|
942
|
+
assert.deepEqual(missing.errors, []);
|
|
943
|
+
assert.equal(missing.warnings.length, 1);
|
|
944
|
+
assert.match(missing.warnings[0], /agent_spawn_triggers\[0\] \('on-schedule'\) spawns agent runs without complete guard config/);
|
|
945
|
+
assert.match(missing.warnings[0], /missing: dedup_key, cooldown_seconds, daily_cap, max_concurrent/);
|
|
946
|
+
assert.match(missing.warnings[0], /context\/contracts\/trigger-guards\.md/);
|
|
947
|
+
assert.equal(missing.entries.length, 1);
|
|
948
|
+
|
|
949
|
+
const incomplete = parseKitAgentSpawnTriggers({
|
|
950
|
+
agent_spawn_triggers: [
|
|
951
|
+
{ id: "on-check-failure", description: "Escalation.", spawns_agent_runs: true, guards: { cooldown_seconds: 900 } },
|
|
952
|
+
],
|
|
953
|
+
}, "fixture/kit.json");
|
|
954
|
+
assert.deepEqual(incomplete.errors, []);
|
|
955
|
+
assert.equal(incomplete.warnings.length, 1);
|
|
956
|
+
assert.match(incomplete.warnings[0], /missing: dedup_key, daily_cap, max_concurrent/);
|
|
957
|
+
|
|
958
|
+
const nonSpawning = parseKitAgentSpawnTriggers({
|
|
959
|
+
agent_spawn_triggers: [
|
|
960
|
+
{ id: "notify-only", description: "Notification only.", spawns_agent_runs: false },
|
|
961
|
+
],
|
|
962
|
+
}, "fixture/kit.json");
|
|
963
|
+
assert.deepEqual(nonSpawning.errors, []);
|
|
964
|
+
assert.deepEqual(nonSpawning.warnings, []);
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
test("agent spawn trigger metadata rejects malformed shapes as errors", () => {
|
|
968
|
+
const notAList = parseKitAgentSpawnTriggers({ agent_spawn_triggers: {} }, "fixture/kit.json");
|
|
969
|
+
assert.match(notAList.errors.join("\n"), /\.agent_spawn_triggers must be a list/);
|
|
970
|
+
|
|
971
|
+
const result = parseKitAgentSpawnTriggers({
|
|
972
|
+
agent_spawn_triggers: [
|
|
973
|
+
{ id: "Bad Id", description: "x", spawns_agent_runs: true },
|
|
974
|
+
{ id: "dup", description: "x", spawns_agent_runs: true, guards: { dedup_key: "sig", cooldown_seconds: 900, daily_cap: 20, max_concurrent: 1 } },
|
|
975
|
+
{ id: "dup", description: "x", spawns_agent_runs: true, guards: { dedup_key: "sig", cooldown_seconds: 900, daily_cap: 20, max_concurrent: 1 } },
|
|
976
|
+
{ id: "no-description", spawns_agent_runs: true },
|
|
977
|
+
{ id: "no-boolean", description: "x", spawns_agent_runs: "yes" },
|
|
978
|
+
{ id: "bad-guards", description: "x", spawns_agent_runs: true, guards: [] },
|
|
979
|
+
{ id: "bad-guard-values", description: "x", spawns_agent_runs: true, guards: { dedup_key: " ", cooldown_seconds: 0, daily_cap: 1.5, max_concurrent: -1 } },
|
|
980
|
+
{ id: "unknown-guard", description: "x", spawns_agent_runs: true, guards: { dedup_key: "sig", cooldown_seconds: 900, daily_cap: 20, max_concurrent: 1, burst: 5 } },
|
|
981
|
+
{ id: "unknown-field", description: "x", spawns_agent_runs: true, command: "private-bypass" },
|
|
982
|
+
],
|
|
983
|
+
}, "fixture/kit.json");
|
|
984
|
+
|
|
985
|
+
const errors = result.errors.join("\n");
|
|
986
|
+
assert.match(errors, /agent_spawn_triggers\[0\]\.id must match/);
|
|
987
|
+
assert.match(errors, /agent_spawn_triggers\[2\]\.id duplicates 'dup'/);
|
|
988
|
+
assert.match(errors, /agent_spawn_triggers\[3\]\.description must be a non-empty string/);
|
|
989
|
+
assert.match(errors, /agent_spawn_triggers\[4\]\.spawns_agent_runs must be a boolean/);
|
|
990
|
+
assert.match(errors, /agent_spawn_triggers\[5\]\.guards must be an object/);
|
|
991
|
+
assert.match(errors, /agent_spawn_triggers\[6\]\.guards\.dedup_key must be a non-empty string/);
|
|
992
|
+
assert.match(errors, /agent_spawn_triggers\[6\]\.guards\.cooldown_seconds must be an integer >= 1/);
|
|
993
|
+
assert.match(errors, /agent_spawn_triggers\[6\]\.guards\.daily_cap must be an integer >= 1/);
|
|
994
|
+
assert.match(errors, /agent_spawn_triggers\[6\]\.guards\.max_concurrent must be an integer >= 1/);
|
|
995
|
+
assert.match(errors, /agent_spawn_triggers\[7\]\.guards contains unsupported field\(s\): burst/);
|
|
996
|
+
assert.match(errors, /agent_spawn_triggers\[8\] contains unsupported field\(s\): command/);
|
|
997
|
+
// Malformed entries fail shape validation; only the first 'dup' entry (valid shape) parses.
|
|
998
|
+
assert.equal(result.entries.length, 1);
|
|
999
|
+
// Shape violations are errors, never demoted to the guard-completeness warning channel.
|
|
1000
|
+
assert.deepEqual(result.warnings, []);
|
|
1001
|
+
});
|
|
1002
|
+
|
|
1003
|
+
test("kit repository diagnostics carry the guardless-spawn warning and never leak it into errors", async () => {
|
|
1004
|
+
const root = tempRoot("flow-agents-agent-spawn-trigger-repo-");
|
|
1005
|
+
const kit = path.join(root, "builder");
|
|
1006
|
+
fs.cpSync("kits/builder", kit, { recursive: true });
|
|
1007
|
+
const manifestFile = path.join(kit, "kit.json");
|
|
1008
|
+
const manifest = JSON.parse(fs.readFileSync(manifestFile, "utf8"));
|
|
1009
|
+
manifest.agent_spawn_triggers = [
|
|
1010
|
+
{ id: "on-check-failure", description: "Escalates failing checks to a headless agent run.", spawns_agent_runs: true },
|
|
1011
|
+
];
|
|
1012
|
+
writeJson(manifestFile, manifest);
|
|
1013
|
+
|
|
1014
|
+
const diagnostics = await validateKitRepositoryDiagnostics(kit);
|
|
1015
|
+
assert.deepEqual(diagnostics.errors, []);
|
|
1016
|
+
assert.equal(diagnostics.warnings.length, 1);
|
|
1017
|
+
assert.match(diagnostics.warnings[0], /spawns agent runs without complete guard config/);
|
|
1018
|
+
// validateKitRepository keeps its errors-only contract: a warning is not an error.
|
|
1019
|
+
assert.deepEqual(await validateKitRepository(kit), []);
|
|
1020
|
+
|
|
1021
|
+
// A fully guarded declaration is clean on both channels.
|
|
1022
|
+
manifest.agent_spawn_triggers[0].guards = { dedup_key: "check-name+failure-signature", cooldown_seconds: 900, daily_cap: 20, max_concurrent: 1 };
|
|
1023
|
+
writeJson(manifestFile, manifest);
|
|
1024
|
+
const guarded = await validateKitRepositoryDiagnostics(kit);
|
|
1025
|
+
assert.deepEqual(guarded.errors, []);
|
|
1026
|
+
assert.deepEqual(guarded.warnings, []);
|
|
1027
|
+
|
|
1028
|
+
// agent_spawn_triggers is recognized Flow Agents metadata, not a third-party namespace.
|
|
1029
|
+
const { deriveKitTargets } = await import("../../build/src/flow-kit/validate.js");
|
|
1030
|
+
const targets = await deriveKitTargets(manifest, kit);
|
|
1031
|
+
assert.deepEqual(targets.third_party_extensions, []);
|
|
1032
|
+
});
|
|
1033
|
+
|
|
1034
|
+
test("kit install surfaces the guardless-spawn warning non-blockingly for BOTH source forms (local path and git URL)", async () => {
|
|
1035
|
+
const { execFileSync } = await import("node:child_process");
|
|
1036
|
+
const root = tempRoot("flow-agents-agent-spawn-trigger-install-");
|
|
1037
|
+
const kitSource = path.join(root, "guarded-kit");
|
|
1038
|
+
fs.mkdirSync(path.join(kitSource, "flows"), { recursive: true });
|
|
1039
|
+
writeJson(path.join(kitSource, "kit.json"), {
|
|
1040
|
+
schema_version: "1.0",
|
|
1041
|
+
id: "guardless-demo",
|
|
1042
|
+
name: "Guardless Demo Kit",
|
|
1043
|
+
flows: [{ id: "demo.flow", path: "flows/demo.flow.json" }],
|
|
1044
|
+
agent_spawn_triggers: [
|
|
1045
|
+
{ id: "on-check-failure", description: "Escalates failing checks to a headless agent run.", spawns_agent_runs: true },
|
|
1046
|
+
],
|
|
1047
|
+
});
|
|
1048
|
+
writeJson(path.join(kitSource, "flows", "demo.flow.json"), {
|
|
1049
|
+
id: "demo.flow",
|
|
1050
|
+
version: "1.0",
|
|
1051
|
+
steps: [{ id: "only", next: null }],
|
|
1052
|
+
gates: {},
|
|
1053
|
+
});
|
|
1054
|
+
const cli = path.resolve("build/src/cli.js");
|
|
1055
|
+
const runInstall = (source, destName) => {
|
|
1056
|
+
return execFileSync(process.execPath, [cli, "kit", "install", source, "--dest", path.join(root, destName)], { encoding: "utf8" });
|
|
1057
|
+
};
|
|
1058
|
+
|
|
1059
|
+
// Local-path source form.
|
|
1060
|
+
const localOut = runInstall(kitSource, "dest-local");
|
|
1061
|
+
assert.match(localOut, /warning: .*agent_spawn_triggers\[0\] \('on-check-failure'\) spawns agent runs without complete guard config/);
|
|
1062
|
+
assert.match(localOut, /installed local kit 'guardless-demo'/);
|
|
1063
|
+
|
|
1064
|
+
// Git-URL source form (file:// clone of the same kit).
|
|
1065
|
+
const git = (args, cwd) => execFileSync("git", ["-c", "user.email=kit-test@example.invalid", "-c", "user.name=kit-test", ...args], { cwd, encoding: "utf8" });
|
|
1066
|
+
git(["init", "--quiet"], kitSource);
|
|
1067
|
+
git(["add", "-A"], kitSource);
|
|
1068
|
+
git(["commit", "--quiet", "-m", "kit fixture"], kitSource);
|
|
1069
|
+
const gitOut = runInstall(`file://${kitSource}`, "dest-git");
|
|
1070
|
+
assert.match(gitOut, /warning: .*agent_spawn_triggers\[0\] \('on-check-failure'\) spawns agent runs without complete guard config/);
|
|
1071
|
+
assert.match(gitOut, /installed git kit 'guardless-demo'/);
|
|
1072
|
+
});
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { spawnSync } from "node:child_process";
|
|
7
|
+
|
|
8
|
+
import { validateKitRepository } from "../../build/src/flow-kit/validate.js";
|
|
9
|
+
import { provisionKit, ProvisionConflictError } from "../../build/src/flow-kit/provision.js";
|
|
10
|
+
|
|
11
|
+
const FLOW = {
|
|
12
|
+
id: "fixture.review",
|
|
13
|
+
version: "1.0",
|
|
14
|
+
steps: [{ id: "review", next: "done" }, { id: "done", next: null }],
|
|
15
|
+
gates: {},
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function fixture(provisions) {
|
|
19
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "kit-provision-unit-"));
|
|
20
|
+
fs.mkdirSync(path.join(dir, "flows"));
|
|
21
|
+
fs.mkdirSync(path.join(dir, "payload"));
|
|
22
|
+
fs.writeFileSync(path.join(dir, "flows", "review.flow.json"), JSON.stringify(FLOW));
|
|
23
|
+
fs.writeFileSync(path.join(dir, "payload", "one.txt"), "one-new\n");
|
|
24
|
+
fs.writeFileSync(path.join(dir, "payload", "two.txt"), "two-new\n");
|
|
25
|
+
fs.writeFileSync(path.join(dir, "kit.json"), JSON.stringify({
|
|
26
|
+
schema_version: "1.0",
|
|
27
|
+
id: "fixture",
|
|
28
|
+
name: "Fixture",
|
|
29
|
+
flows: [{ id: "fixture.review", path: "flows/review.flow.json" }],
|
|
30
|
+
provisions,
|
|
31
|
+
}));
|
|
32
|
+
return dir;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const entry = (id, source, target) => ({ id: `fixture.${id}`, path: `payload/${source}`, target });
|
|
36
|
+
|
|
37
|
+
test("provision validation rejects unsafe and duplicate normalized targets", async () => {
|
|
38
|
+
const cases = [
|
|
39
|
+
[[entry("one", "one.txt", "../escape.txt")], "traversal segments"],
|
|
40
|
+
[[entry("one", "one.txt", path.resolve("/tmp/escape.txt"))], "must be relative"],
|
|
41
|
+
[[entry("one", "one.txt", ".git/config")], "must not be inside .git"],
|
|
42
|
+
[[entry("one", "one.txt", ".GIT/hooks/pre-push")], "must not be inside .git"],
|
|
43
|
+
[[entry("one", "one.txt", "docs//same.txt"), entry("two", "two.txt", "docs/same.txt")], "target duplicates"],
|
|
44
|
+
[[entry("one", "one.txt", "README.md"), entry("two", "two.txt", "readme.md")], "target duplicates"],
|
|
45
|
+
[[entry("one", "one.txt", ".kontourai/flow-agents/provisions/fixture.json")], "provision manifest namespace"],
|
|
46
|
+
[[{ id: "fixture.one", path: "payload/../payload/one.txt", target: "out.txt" }], "must not contain traversal segments"],
|
|
47
|
+
];
|
|
48
|
+
for (const [provisions, message] of cases) {
|
|
49
|
+
const errors = await validateKitRepository(fixture(provisions));
|
|
50
|
+
assert.equal(errors.some((error) => error.includes(message)), true, errors.join("\n"));
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("provision core preflights conflicts before writing and force overwrites", async () => {
|
|
55
|
+
const kit = fixture([entry("one", "one.txt", "docs/one.txt"), entry("two", "two.txt", "docs/two.txt")]);
|
|
56
|
+
const target = fs.mkdtempSync(path.join(os.tmpdir(), "kit-provision-target-"));
|
|
57
|
+
fs.mkdirSync(path.join(target, "docs"));
|
|
58
|
+
fs.writeFileSync(path.join(target, "docs", "two.txt"), "keep\n");
|
|
59
|
+
|
|
60
|
+
await assert.rejects(() => provisionKit(kit, target), (error) => error instanceof ProvisionConflictError && error.conflicts.length === 1);
|
|
61
|
+
assert.equal(fs.existsSync(path.join(target, "docs", "one.txt")), false);
|
|
62
|
+
assert.equal(fs.readFileSync(path.join(target, "docs", "two.txt"), "utf8"), "keep\n");
|
|
63
|
+
|
|
64
|
+
const result = await provisionKit(kit, target, { force: true });
|
|
65
|
+
assert.equal(fs.readFileSync(path.join(target, "docs", "one.txt"), "utf8"), "one-new\n");
|
|
66
|
+
assert.equal(fs.readFileSync(path.join(target, "docs", "two.txt"), "utf8"), "two-new\n");
|
|
67
|
+
const manifest = JSON.parse(fs.readFileSync(result.manifest_path, "utf8"));
|
|
68
|
+
assert.equal(manifest.schema_version, "1.0");
|
|
69
|
+
assert.equal(manifest.kit_id, "fixture");
|
|
70
|
+
assert.match(manifest.kit_hash, /^sha256:[a-f0-9]{64}$/);
|
|
71
|
+
assert.deepEqual(manifest.files, [{ id: "fixture.one", target: "docs/one.txt" }, { id: "fixture.two", target: "docs/two.txt" }]);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("provision core dry-run writes neither files nor manifest", async () => {
|
|
75
|
+
const kit = fixture([entry("one", "one.txt", "docs/one.txt")]);
|
|
76
|
+
const target = fs.mkdtempSync(path.join(os.tmpdir(), "kit-provision-dry-"));
|
|
77
|
+
const result = await provisionKit(kit, target, { dryRun: true });
|
|
78
|
+
assert.equal(result.dry_run, true);
|
|
79
|
+
assert.equal(fs.existsSync(path.join(target, "docs", "one.txt")), false);
|
|
80
|
+
assert.equal(fs.existsSync(path.join(target, ".kontourai")), false);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("provision core rejects destination paths whose existing ancestor escapes through a symlink", async () => {
|
|
84
|
+
const kit = fixture([entry("one", "one.txt", "linked/one.txt")]);
|
|
85
|
+
const target = fs.mkdtempSync(path.join(os.tmpdir(), "kit-provision-link-target-"));
|
|
86
|
+
const outside = fs.mkdtempSync(path.join(os.tmpdir(), "kit-provision-link-outside-"));
|
|
87
|
+
fs.symlinkSync(outside, path.join(target, "linked"), "dir");
|
|
88
|
+
await assert.rejects(() => provisionKit(kit, target), /escapes consumer repository/);
|
|
89
|
+
assert.equal(fs.existsSync(path.join(outside, "one.txt")), false);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("provision rejects a source that resolves outside the kit through a symlink", async () => {
|
|
93
|
+
const kit = fixture([entry("host", "host.txt", "copied.txt")]);
|
|
94
|
+
const outside = fs.mkdtempSync(path.join(os.tmpdir(), "kit-provision-src-outside-"));
|
|
95
|
+
const secret = path.join(outside, "secret.txt");
|
|
96
|
+
fs.writeFileSync(secret, "off-kit-secret\n");
|
|
97
|
+
fs.symlinkSync(secret, path.join(kit, "payload", "host.txt"));
|
|
98
|
+
|
|
99
|
+
const errors = await validateKitRepository(kit);
|
|
100
|
+
assert.equal(errors.some((error) => error.includes("must not resolve outside the kit directory")), true, errors.join("\n"));
|
|
101
|
+
|
|
102
|
+
const target = fs.mkdtempSync(path.join(os.tmpdir(), "kit-provision-src-target-"));
|
|
103
|
+
await assert.rejects(() => provisionKit(kit, target), /escapes the kit directory|validation failed/);
|
|
104
|
+
assert.equal(fs.existsSync(path.join(target, "copied.txt")), false);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("init activation provisions create-only and reports rerun conflicts without failing", () => {
|
|
108
|
+
const kit = fixture([entry("one", "one.txt", "docs/one.txt")]);
|
|
109
|
+
const target = fs.mkdtempSync(path.join(os.tmpdir(), "kit-provision-init-"));
|
|
110
|
+
const install = spawnSync(process.execPath, ["build/src/cli.js", "kit", "install", kit, "--dest", target], { encoding: "utf8" });
|
|
111
|
+
assert.equal(install.status, 0, `${install.stdout}\n${install.stderr}`);
|
|
112
|
+
const args = ["build/src/cli.js", "init", "--runtime", "codex", "--dest", target, "--telemetry-sink", "local-files", "--activate-kit", "fixture", "--yes"];
|
|
113
|
+
const first = spawnSync(process.execPath, args, { encoding: "utf8" });
|
|
114
|
+
assert.equal(first.status, 0, `${first.stdout}\n${first.stderr}`);
|
|
115
|
+
assert.equal(fs.readFileSync(path.join(target, "docs", "one.txt"), "utf8"), "one-new\n");
|
|
116
|
+
|
|
117
|
+
fs.writeFileSync(path.join(target, "docs", "one.txt"), "consumer-owned\n");
|
|
118
|
+
const second = spawnSync(process.execPath, args, { encoding: "utf8" });
|
|
119
|
+
assert.equal(second.status, 0, `${second.stdout}\n${second.stderr}`);
|
|
120
|
+
assert.match(`${second.stdout}\n${second.stderr}`, /skipped existing provision 'docs\/one.txt'/);
|
|
121
|
+
assert.equal(fs.readFileSync(path.join(target, "docs", "one.txt"), "utf8"), "consumer-owned\n");
|
|
122
|
+
});
|
package/src/cli/kit.ts
CHANGED
|
@@ -6,7 +6,8 @@ import * as path from "node:path";
|
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { parseArgs, flagBool, flagString } from "../lib/args.js";
|
|
8
8
|
import { assertPathContained, assertPathsDisjoint, atomicWriteJson, copyDirAtomic, ensureSafeDirectory, isoNow, readJson, walkFiles } from "../lib/fs.js";
|
|
9
|
-
import { assertKitRepository, deriveKitTargets, parseKitDependencies } from "../flow-kit/validate.js";
|
|
9
|
+
import { assertKitRepository, deriveKitTargets, parseKitDependencies, validateKitRepositoryDiagnostics } from "../flow-kit/validate.js";
|
|
10
|
+
import { provisionKit, ProvisionConflictError } from "../flow-kit/provision.js";
|
|
10
11
|
import { activateCodexLocal, activateStrandsLocal } from "../runtime-adapters.js";
|
|
11
12
|
import { defaultCodexHome } from "../lib/local-artifact-root.js";
|
|
12
13
|
import { root } from "../tools/common.js";
|
|
@@ -35,6 +36,17 @@ function resolveCatalogKitSource(source: string): string | null {
|
|
|
35
36
|
return path.resolve(path.dirname(repoCatalogPath), "..", rel);
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
function resolveProvisionKitSource(source: string, dest: string): string | null {
|
|
40
|
+
const localPath = path.resolve(source);
|
|
41
|
+
if (fs.existsSync(localPath)) return localPath;
|
|
42
|
+
const installed = loadRegistry(dest).kits.find((entry) => entry.id === source);
|
|
43
|
+
if (installed) {
|
|
44
|
+
const installedSource = installedPath(dest, source);
|
|
45
|
+
if (fs.existsSync(installedSource)) return installedSource;
|
|
46
|
+
}
|
|
47
|
+
return resolveCatalogKitSource(source);
|
|
48
|
+
}
|
|
49
|
+
|
|
38
50
|
function loadRegistry(dest: string): { schema_version: string; kits: Record<string, unknown>[] } {
|
|
39
51
|
const file = registryPath(dest);
|
|
40
52
|
if (!fs.existsSync(file)) return { schema_version: "1.0", kits: [] };
|
|
@@ -64,6 +76,17 @@ function warnUninstalledDependencies(manifest: Record<string, unknown>, manifest
|
|
|
64
76
|
}
|
|
65
77
|
}
|
|
66
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Print non-blocking kit-validation warnings (e.g. an agent-spawning trigger surface
|
|
81
|
+
* declared without complete guard config — context/contracts/trigger-guards.md).
|
|
82
|
+
* Shared by BOTH install source forms (local path and git clone) so the standing
|
|
83
|
+
* warning contract cannot silently diverge between them. Same non-blocking
|
|
84
|
+
* `warning:` convention as warnUninstalledDependencies above.
|
|
85
|
+
*/
|
|
86
|
+
async function printKitValidationWarnings(kitDir: string): Promise<void> {
|
|
87
|
+
for (const warning of (await validateKitRepositoryDiagnostics(kitDir)).warnings) console.log(`warning: ${warning}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
67
90
|
function contentHash(root: string): string {
|
|
68
91
|
const hash = crypto.createHash("sha256");
|
|
69
92
|
for (const file of walkFiles(root)) {
|
|
@@ -126,6 +149,7 @@ async function installLocalSource(source: string, argv: string[]): Promise<numbe
|
|
|
126
149
|
let manifest: Record<string, unknown>;
|
|
127
150
|
try {
|
|
128
151
|
manifest = await assertKitRepository(source);
|
|
152
|
+
await printKitValidationWarnings(source);
|
|
129
153
|
} catch (error) {
|
|
130
154
|
console.log("Flow Kit repository validation failed:");
|
|
131
155
|
for (const diagnostic of ((error as Error & { diagnostics?: string[] }).diagnostics ?? [(error as Error).message])) console.log(` - ${diagnostic}`);
|
|
@@ -213,6 +237,7 @@ async function installGitSource(rawUrl: string, argv: string[]): Promise<number>
|
|
|
213
237
|
let manifest: Record<string, unknown>;
|
|
214
238
|
try {
|
|
215
239
|
manifest = await assertKitRepository(tmpBase);
|
|
240
|
+
await printKitValidationWarnings(tmpBase);
|
|
216
241
|
} catch (error) {
|
|
217
242
|
console.log("Flow Kit repository validation failed:");
|
|
218
243
|
for (const diagnostic of ((error as Error & { diagnostics?: string[] }).diagnostics ?? [(error as Error).message])) {
|
|
@@ -325,6 +350,60 @@ function activate(argv: string[]): number {
|
|
|
325
350
|
return Array.isArray(result.errors) && result.errors.length ? 1 : 0;
|
|
326
351
|
}
|
|
327
352
|
|
|
353
|
+
async function validate(argv: string[]): Promise<number> {
|
|
354
|
+
const args = parseArgs(argv);
|
|
355
|
+
const kitDir = path.resolve(args.positionals[0] ?? ".");
|
|
356
|
+
const diagnostics = await validateKitRepositoryDiagnostics(kitDir);
|
|
357
|
+
for (const warning of diagnostics.warnings) console.log(`warning: ${warning}`);
|
|
358
|
+
if (diagnostics.errors.length) {
|
|
359
|
+
console.log("Flow Kit repository validation failed:");
|
|
360
|
+
for (const error of diagnostics.errors) console.log(` - ${error}`);
|
|
361
|
+
return 1;
|
|
362
|
+
}
|
|
363
|
+
console.log(`Flow Kit repository validation passed: ${kitDir}`);
|
|
364
|
+
return 0;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
async function provision(argv: string[]): Promise<number> {
|
|
368
|
+
const args = parseArgs(argv);
|
|
369
|
+
const source = args.positionals[0];
|
|
370
|
+
if (!source) {
|
|
371
|
+
console.error("provision: missing <kit-id-or-path> argument");
|
|
372
|
+
console.error("usage: flow-agents kit provision <kit-id-or-path> [--target <dir>] [--force] [--dry-run]");
|
|
373
|
+
return 2;
|
|
374
|
+
}
|
|
375
|
+
const dest = resolveDest(args.flags);
|
|
376
|
+
const kitDir = resolveProvisionKitSource(source, dest);
|
|
377
|
+
if (!kitDir) {
|
|
378
|
+
console.error(`provision: kit '${source}' was not found as a local path, installed kit, or catalog kit`);
|
|
379
|
+
return 1;
|
|
380
|
+
}
|
|
381
|
+
const target = path.resolve(flagString(args.flags, "target", process.cwd()) ?? process.cwd());
|
|
382
|
+
try {
|
|
383
|
+
const result = await provisionKit(kitDir, target, {
|
|
384
|
+
force: flagBool(args.flags, "force"),
|
|
385
|
+
dryRun: flagBool(args.flags, "dry-run"),
|
|
386
|
+
});
|
|
387
|
+
for (const file of result.files) console.log(`${result.dry_run ? "would provision" : "provisioned"}: ${file.source} -> ${file.destination}`);
|
|
388
|
+
if (result.dry_run) console.log(`dry-run: ${result.files.length} file(s) declared by kit '${result.kit_id}'; no files written`);
|
|
389
|
+
else if (result.files.length === 0) console.log(`kit '${result.kit_id}' declares no provisions`);
|
|
390
|
+
else console.log(`provisioned ${result.files.length} file(s) from kit '${result.kit_id}' into ${target}`);
|
|
391
|
+
return 0;
|
|
392
|
+
} catch (error) {
|
|
393
|
+
if (error instanceof ProvisionConflictError) {
|
|
394
|
+
console.error(`provision: ${error.message}; rerun with --force to overwrite`);
|
|
395
|
+
for (const conflict of error.conflicts) console.error(`conflict: ${conflict.target} (${conflict.destination})`);
|
|
396
|
+
return 1;
|
|
397
|
+
}
|
|
398
|
+
const diagnostics = (error as Error & { diagnostics?: string[] }).diagnostics;
|
|
399
|
+
if (diagnostics?.length) {
|
|
400
|
+
console.error("Flow Kit repository validation failed:");
|
|
401
|
+
for (const diagnostic of diagnostics) console.error(` - ${diagnostic}`);
|
|
402
|
+
} else console.error(`provision: ${(error as Error).message}`);
|
|
403
|
+
return 1;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
328
407
|
/**
|
|
329
408
|
* inspect <kit-dir> [--json]
|
|
330
409
|
*
|
|
@@ -373,8 +452,10 @@ export async function main(argv = process.argv.slice(2)): Promise<number> {
|
|
|
373
452
|
if (command === "list") return list(rest);
|
|
374
453
|
if (command === "status") return status(rest);
|
|
375
454
|
if (command === "activate") return activate(rest);
|
|
455
|
+
if (command === "validate") return await validate(rest);
|
|
456
|
+
if (command === "provision") return await provision(rest);
|
|
376
457
|
if (command === "inspect") return await inspect(rest);
|
|
377
|
-
console.error("usage: flow-agents kit <install|activate|inspect|list|status> ...");
|
|
458
|
+
console.error("usage: flow-agents kit <install|activate|validate|provision|inspect|list|status> ...");
|
|
378
459
|
return 2;
|
|
379
460
|
}
|
|
380
461
|
|
|
@@ -849,7 +849,13 @@ export function main(argv = process.argv.slice(2)): number {
|
|
|
849
849
|
const freshness = revisionFreshness(item, freshnessInputs);
|
|
850
850
|
return { ...item, dependency_impacts: dependencyImpacts(item, resolved), revision_freshness: freshness, readiness: applyFreshnessToReadiness(classify(item, settings, resolved), freshness) };
|
|
851
851
|
});
|
|
852
|
-
|
|
852
|
+
// A configured BoardProvider is the canonical readiness source (docs/decisions/backlog-readiness-source.md).
|
|
853
|
+
// Reaching this issue-level listing path with a board configured is a bypass that must be loud, never silent.
|
|
854
|
+
const bypassedBoard = asRecord(asRecord(settings.board_provider)?.board);
|
|
855
|
+
const warnings = bypassedBoard
|
|
856
|
+
? [{ code: "board_provider_bypassed", message: "A BoardProvider is configured but this pass used WorkItemProvider issue-level listing; board-driven selection is the canonical readiness source. Surface this bypass in the pull-work artifact instead of treating issue listing as a silent fallback." }]
|
|
857
|
+
: [];
|
|
858
|
+
console.log(JSON.stringify({ items, warnings }, null, 2));
|
|
853
859
|
return 0;
|
|
854
860
|
}
|
|
855
861
|
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// Unit tests for waves.json wave-manifest reconciliation validation (#663 slice 1).
|
|
2
|
+
//
|
|
3
|
+
// validate-workflow-artifacts.ts is a CLI script (no exported helpers), so these
|
|
4
|
+
// node:test units drive the built validator as a child process against temp fixture
|
|
5
|
+
// directories — the same black-box surface CI's workflow-artifact-integration lane
|
|
6
|
+
// uses, but fast and countable as unit executions. They pin the load-bearing
|
|
7
|
+
// behavior of the reconcile-against-manifest contract:
|
|
8
|
+
// - a reconciled wave with an explicit not_reported worker is VALID DATA
|
|
9
|
+
// ("2 of 3 reported; worker-3 not_reported") — visibility, not a block;
|
|
10
|
+
// - a silent drop (complete claim over a missing worker record) FAILS with
|
|
11
|
+
// messages naming the dropped worker, the count mismatch, and the impossible
|
|
12
|
+
// complete claim.
|
|
13
|
+
//
|
|
14
|
+
// Run: `node --test src/cli/validate-waves.test.mjs` (requires `npm run build`).
|
|
15
|
+
import test from "node:test";
|
|
16
|
+
import assert from "node:assert/strict";
|
|
17
|
+
import { spawnSync } from "node:child_process";
|
|
18
|
+
import fs from "node:fs";
|
|
19
|
+
import os from "node:os";
|
|
20
|
+
import path from "node:path";
|
|
21
|
+
import { fileURLToPath } from "node:url";
|
|
22
|
+
|
|
23
|
+
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
24
|
+
const validator = path.join(repoRoot, "build", "src", "cli", "validate-workflow-artifacts.js");
|
|
25
|
+
|
|
26
|
+
function runValidator(wavesPayload) {
|
|
27
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "waves-unit-"));
|
|
28
|
+
try {
|
|
29
|
+
fs.writeFileSync(path.join(dir, "waves.json"), JSON.stringify(wavesPayload, null, 2));
|
|
30
|
+
const result = spawnSync(process.execPath, [validator, dir], { encoding: "utf8" });
|
|
31
|
+
return { status: result.status, output: `${result.stdout}\n${result.stderr}` };
|
|
32
|
+
} finally {
|
|
33
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function manifest(waveOverrides) {
|
|
38
|
+
return {
|
|
39
|
+
schema_version: "1.0",
|
|
40
|
+
task_slug: "waves-unit",
|
|
41
|
+
waves: [
|
|
42
|
+
{
|
|
43
|
+
wave_id: "execute-wave-1",
|
|
44
|
+
step: "execute",
|
|
45
|
+
declared_at: "2026-07-16T00:00:00Z",
|
|
46
|
+
expected_workers: [
|
|
47
|
+
{ worker_id: "worker-1", task: "task one" },
|
|
48
|
+
{ worker_id: "worker-2", task: "task two" },
|
|
49
|
+
{ worker_id: "worker-3", task: "task three" },
|
|
50
|
+
],
|
|
51
|
+
...waveOverrides,
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
test("fully reported wave reconciles complete and validates", () => {
|
|
58
|
+
const { status, output } = runValidator(manifest({
|
|
59
|
+
worker_results: [
|
|
60
|
+
{ worker_id: "worker-1", status: "completed", summary: "done", recorded_at: "2026-07-16T01:00:00Z" },
|
|
61
|
+
{ worker_id: "worker-2", status: "failed", summary: "tests failing", recorded_at: "2026-07-16T01:00:00Z" },
|
|
62
|
+
{ worker_id: "worker-3", status: "blocked", summary: "blocked on approval", recorded_at: "2026-07-16T01:00:00Z" },
|
|
63
|
+
],
|
|
64
|
+
reconciliation: {
|
|
65
|
+
status: "complete",
|
|
66
|
+
expected_count: 3,
|
|
67
|
+
reported_count: 3,
|
|
68
|
+
summary: "3 of 3 reported; no workers not_reported",
|
|
69
|
+
reconciled_at: "2026-07-16T01:05:00Z",
|
|
70
|
+
},
|
|
71
|
+
}));
|
|
72
|
+
assert.equal(status, 0, output);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("2 of 3 reported with explicit not_reported worker is valid data, not a block", () => {
|
|
76
|
+
const { status, output } = runValidator(manifest({
|
|
77
|
+
worker_results: [
|
|
78
|
+
{ worker_id: "worker-1", status: "completed", summary: "done", recorded_at: "2026-07-16T01:00:00Z" },
|
|
79
|
+
{ worker_id: "worker-2", status: "completed", summary: "done", recorded_at: "2026-07-16T01:00:00Z" },
|
|
80
|
+
{ worker_id: "worker-3", status: "not_reported", summary: "no terminal report by wave close; routed to re-dispatch", recorded_at: "2026-07-16T01:05:00Z" },
|
|
81
|
+
],
|
|
82
|
+
reconciliation: {
|
|
83
|
+
status: "incomplete",
|
|
84
|
+
expected_count: 3,
|
|
85
|
+
reported_count: 2,
|
|
86
|
+
summary: "2 of 3 reported; worker-3 not_reported",
|
|
87
|
+
reconciled_at: "2026-07-16T01:05:00Z",
|
|
88
|
+
},
|
|
89
|
+
}));
|
|
90
|
+
assert.equal(status, 0, output);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("silent drop behind a complete claim fails naming the missing worker", () => {
|
|
94
|
+
const { status, output } = runValidator(manifest({
|
|
95
|
+
worker_results: [
|
|
96
|
+
{ worker_id: "worker-1", status: "completed", summary: "done", recorded_at: "2026-07-16T01:00:00Z" },
|
|
97
|
+
{ worker_id: "worker-2", status: "completed", summary: "done", recorded_at: "2026-07-16T01:00:00Z" },
|
|
98
|
+
],
|
|
99
|
+
reconciliation: {
|
|
100
|
+
status: "complete",
|
|
101
|
+
expected_count: 3,
|
|
102
|
+
reported_count: 3,
|
|
103
|
+
summary: "3 of 3 reported",
|
|
104
|
+
reconciled_at: "2026-07-16T01:05:00Z",
|
|
105
|
+
},
|
|
106
|
+
}));
|
|
107
|
+
assert.equal(status, 1);
|
|
108
|
+
assert.match(output, /worker worker-3 has no terminal status record/);
|
|
109
|
+
assert.match(output, /reported_count is 3 but 2 of 3 expected workers/);
|
|
110
|
+
assert.match(output, /complete requires every expected worker/);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("duplicate terminal records and undeclared reporters are rejected", () => {
|
|
114
|
+
const { status, output } = runValidator(manifest({
|
|
115
|
+
worker_results: [
|
|
116
|
+
{ worker_id: "worker-1", status: "completed", summary: "done", recorded_at: "2026-07-16T01:00:00Z" },
|
|
117
|
+
{ worker_id: "worker-1", status: "failed", summary: "again", recorded_at: "2026-07-16T01:01:00Z" },
|
|
118
|
+
{ worker_id: "worker-9", status: "completed", summary: "surprise", recorded_at: "2026-07-16T01:00:00Z" },
|
|
119
|
+
],
|
|
120
|
+
}));
|
|
121
|
+
assert.equal(status, 1);
|
|
122
|
+
assert.match(output, /more than one terminal status record for worker worker-1/);
|
|
123
|
+
assert.match(output, /worker worker-9 that is not declared in expected_workers/);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("unknown terminal status fails against the schema enum", () => {
|
|
127
|
+
const { status, output } = runValidator(manifest({
|
|
128
|
+
worker_results: [
|
|
129
|
+
{ worker_id: "worker-1", status: "maybe-done", summary: "unclear", recorded_at: "2026-07-16T01:00:00Z" },
|
|
130
|
+
],
|
|
131
|
+
}));
|
|
132
|
+
assert.equal(status, 1);
|
|
133
|
+
assert.match(output, /status must be one of: completed, failed, blocked, not_reported/);
|
|
134
|
+
});
|
|
@@ -21,6 +21,7 @@ const sidecarSchemas: Record<string, string> = {
|
|
|
21
21
|
"critique.json": "schemas/workflow-critique.schema.json",
|
|
22
22
|
"release.json": "schemas/workflow-release.schema.json",
|
|
23
23
|
"learning.json": "schemas/workflow-learning.schema.json",
|
|
24
|
+
"waves.json": "schemas/workflow-waves.schema.json",
|
|
24
25
|
};
|
|
25
26
|
// Runtime coordination records live below a session but are not workflow
|
|
26
27
|
// sidecars. Recursing into them would validate continuation-driver/state.json
|
|
@@ -321,12 +322,58 @@ function validateSidecar(file: string): { issues: Issue[]; warnings: Issue[] } {
|
|
|
321
322
|
if (Array.isArray(value.post_deploy_checks) && value.post_deploy_checks.some((c: any) => !["planned", "pass"].includes(c.status))) issues.push({ path: file, message: "deploy decision requires post_deploy_checks to be planned or pass" });
|
|
322
323
|
}
|
|
323
324
|
}
|
|
325
|
+
if (path.basename(file) === "waves.json") {
|
|
326
|
+
validateWaves(file, value, issues);
|
|
327
|
+
}
|
|
324
328
|
if (path.basename(file) === "state.json" && value && typeof value === "object" && !Array.isArray(value) && !("branch" in value)) {
|
|
325
329
|
warnings.push({ path: file, message: "state.json has no branch field (legacy/pre-#289 session) — re-run `ensure-session` to backfill agent/<actor>/<slug>." });
|
|
326
330
|
}
|
|
327
331
|
return { issues, warnings };
|
|
328
332
|
}
|
|
329
333
|
|
|
334
|
+
// #663 slice 1: wave-result reconciliation is machine-checkable, not prose. The schema
|
|
335
|
+
// (schemas/workflow-waves.schema.json) validates the shape; these semantic checks enforce the
|
|
336
|
+
// cross-record arithmetic the mini schema engine cannot express: exactly one terminal status
|
|
337
|
+
// record per declared worker, no undeclared reporters, reconciliation counts that match the
|
|
338
|
+
// records, and a `complete` claim that is impossible while any expected worker lacks a
|
|
339
|
+
// worker-landed terminal status. Missing workers must be recorded explicitly as not_reported —
|
|
340
|
+
// a reconciled wave never silently absorbs a dropped worker. Enforcement here is data-level
|
|
341
|
+
// validation only; stop-hook refusal of wave completion is #663 part 3 (slice 2).
|
|
342
|
+
function validateWaves(file: string, value: any, issues: Issue[]): void {
|
|
343
|
+
const waves = Array.isArray(value?.waves) ? value.waves : [];
|
|
344
|
+
waves.forEach((wave: any, index: number) => {
|
|
345
|
+
const loc = `waves.json.waves[${index}]`;
|
|
346
|
+
const expected: any[] = Array.isArray(wave?.expected_workers) ? wave.expected_workers : [];
|
|
347
|
+
const results: any[] = Array.isArray(wave?.worker_results) ? wave.worker_results : [];
|
|
348
|
+
const expectedIds = expected.map((w) => w?.worker_id).filter((id) => typeof id === "string" && id.length > 0);
|
|
349
|
+
const expectedIdSet = new Set(expectedIds);
|
|
350
|
+
if (expectedIdSet.size !== expectedIds.length) issues.push({ path: file, message: `${loc}.expected_workers must declare unique worker_id values` });
|
|
351
|
+
const resultIds = results.map((r) => r?.worker_id).filter((id) => typeof id === "string" && id.length > 0);
|
|
352
|
+
const seen = new Set<string>();
|
|
353
|
+
for (const id of resultIds) {
|
|
354
|
+
if (seen.has(id)) issues.push({ path: file, message: `${loc}.worker_results has more than one terminal status record for worker ${id}; each worker lands exactly one` });
|
|
355
|
+
seen.add(id);
|
|
356
|
+
if (!expectedIdSet.has(id)) issues.push({ path: file, message: `${loc}.worker_results records worker ${id} that is not declared in expected_workers; declare every worker in the manifest before dispatch` });
|
|
357
|
+
}
|
|
358
|
+
const reconciliation = wave?.reconciliation;
|
|
359
|
+
if (!reconciliation || typeof reconciliation !== "object" || Array.isArray(reconciliation)) return;
|
|
360
|
+
const expectedCount = expectedIds.length;
|
|
361
|
+
const reportedIds = new Set(results.filter((r) => ["completed", "failed", "blocked"].includes(r?.status) && expectedIdSet.has(r?.worker_id)).map((r) => r.worker_id as string));
|
|
362
|
+
const reportedCount = reportedIds.size;
|
|
363
|
+
if (reconciliation.expected_count !== expectedCount) issues.push({ path: file, message: `${loc}.reconciliation.expected_count is ${reconciliation.expected_count} but expected_workers declares ${expectedCount}` });
|
|
364
|
+
if (reconciliation.reported_count !== reportedCount) issues.push({ path: file, message: `${loc}.reconciliation.reported_count is ${reconciliation.reported_count} but ${reportedCount} of ${expectedCount} expected workers have a worker-landed terminal status (completed|failed|blocked)` });
|
|
365
|
+
const missing = expectedIds.filter((id) => !results.some((r) => r?.worker_id === id));
|
|
366
|
+
for (const id of missing) issues.push({ path: file, message: `${loc} is reconciled but worker ${id} has no terminal status record; record it explicitly as not_reported — never silently absorb a missing worker` });
|
|
367
|
+
const notReported = results.filter((r) => r?.status === "not_reported" && expectedIdSet.has(r?.worker_id));
|
|
368
|
+
if (reconciliation.status === "complete" && (missing.length > 0 || notReported.length > 0 || reportedCount !== expectedCount)) {
|
|
369
|
+
issues.push({ path: file, message: `${loc}.reconciliation.status complete requires every expected worker to have a worker-landed terminal status; got ${reportedCount} of ${expectedCount} reported` });
|
|
370
|
+
}
|
|
371
|
+
if (reconciliation.status === "incomplete" && missing.length === 0 && notReported.length === 0 && reportedCount === expectedCount) {
|
|
372
|
+
issues.push({ path: file, message: `${loc}.reconciliation.status incomplete contradicts the records: all ${expectedCount} expected workers reported` });
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
|
|
330
377
|
function validateLearningCorrections(file: string, status: unknown, records: any[], issues: Issue[]): void {
|
|
331
378
|
records.forEach((record, index) => {
|
|
332
379
|
const correction = record?.correction;
|