@kontourai/flow-agents 2.3.0 → 3.0.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/CODEOWNERS +16 -0
- package/.github/workflows/ci.yml +44 -0
- package/.github/workflows/trust-reconcile.yml +62 -4
- package/AGENTS.md +4 -4
- package/CHANGELOG.md +62 -0
- package/CONTEXT.md +35 -0
- package/README.md +9 -3
- package/agents/dev.json +1 -1
- package/agents/tool-code-reviewer.json +1 -1
- package/agents/tool-planner.json +3 -3
- package/agents/tool-verifier.json +3 -3
- package/build/src/cli/assignment-provider.d.ts +1 -0
- package/build/src/cli/assignment-provider.js +748 -0
- package/build/src/cli/console-learning-projection.js +3 -2
- package/build/src/cli/effective-assignment-provider-settings.d.ts +1 -0
- package/build/src/cli/effective-assignment-provider-settings.js +125 -0
- package/build/src/cli/init.js +104 -21
- package/build/src/cli/kit.js +37 -6
- package/build/src/cli/pull-work-provider.js +1 -1
- package/build/src/cli/usage-feedback.js +3 -3
- package/build/src/cli/validate-hook-influence.js +1 -0
- package/build/src/cli/validate-workflow-artifacts.js +27 -7
- package/build/src/cli/workflow-artifact-cleanup-audit.js +36 -2
- package/build/src/cli/workflow-sidecar.d.ts +14 -7
- package/build/src/cli/workflow-sidecar.js +938 -168
- package/build/src/cli.js +6 -0
- package/build/src/flow-kit/validate.d.ts +23 -0
- package/build/src/flow-kit/validate.js +67 -1
- package/build/src/index.d.ts +1 -1
- package/build/src/index.js +1 -1
- package/build/src/lib/flow-resolver.d.ts +6 -4
- package/build/src/lib/flow-resolver.js +125 -58
- package/build/src/lib/local-artifact-root.d.ts +14 -2
- package/build/src/lib/local-artifact-root.js +22 -5
- package/build/src/lib/workflow-learning-projection.js +2 -2
- package/build/src/runtime-adapters.d.ts +12 -0
- package/build/src/runtime-adapters.js +56 -16
- package/build/src/tools/build-universal-bundles.js +49 -16
- package/build/src/tools/generate-context-map.js +10 -8
- package/build/src/tools/validate-source-tree.js +24 -1
- package/console.telemetry.json +22 -0
- package/context/contracts/artifact-contract.md +40 -7
- package/context/contracts/assignment-provider-contract.md +239 -0
- package/context/contracts/builder-kit-workflow-state-contract.md +5 -2
- package/context/contracts/decision-registry-contract.md +265 -0
- package/context/contracts/delivery-contract.md +3 -1
- package/context/contracts/execution-contract.md +25 -0
- package/context/contracts/governance-adapter-contract.md +2 -0
- package/context/contracts/knowledge-store-contract.md +197 -0
- package/context/contracts/planning-contract.md +4 -1
- package/context/contracts/review-contract.md +7 -3
- package/context/contracts/sandbox-policy.md +2 -0
- package/context/contracts/standing-directives.md +13 -0
- package/context/contracts/verification-contract.md +11 -1
- package/context/contracts/work-item-contract.md +3 -1
- package/context/deferred/parallelization.md +1 -1
- package/context/gate-awareness.md +4 -4
- package/context/scripts/git-status.sh +2 -2
- package/context/scripts/hooks/config-protection.js +502 -2
- package/context/scripts/hooks/lib/config-protection-remedies.js +69 -0
- package/context/scripts/hooks/run-hook.js +14 -1
- package/context/scripts/hooks/stop-goal-fit.js +1140 -99
- package/context/scripts/hooks/workflow-steering.js +212 -6
- package/context/settings/assignment-provider-settings.json +33 -0
- package/docs/adr/0007-flow-skill-kit-tool-boundary.md +2 -0
- package/docs/adr/0015-flow-flow-agents-boundary-reconciliation.md +35 -0
- package/docs/adr/0019-kit-dependency-ownership.md +62 -0
- package/docs/adr/0020-trust-reconcile-manifest-and-claim-classification.md +290 -0
- package/docs/adr/0021-assignment-leases-and-stale-claim-takeover.md +221 -0
- package/docs/adr/0022-fail-closed-delivery-reconciliation-with-governed-exemptions.md +463 -0
- package/docs/agent-system-guidebook.md +3 -3
- package/docs/agent-usage-feedback-loop.md +6 -6
- package/docs/context-map.md +10 -7
- package/docs/decisions/decision-records.md +57 -0
- package/docs/decisions/index.md +17 -0
- package/docs/decisions/knowledge-store-provider.md +51 -0
- package/docs/decisions/model-routing.md +63 -0
- package/docs/decisions/promotion-gate.md +52 -0
- package/docs/decisions/standing-directives.md +66 -0
- package/docs/developer-architecture.md +1 -1
- package/docs/fixture-ownership.md +4 -0
- package/docs/flow-kit-repository-contract.md +5 -5
- package/docs/getting-started.md +12 -2
- package/docs/integrations/harness-install.md +2 -1
- package/docs/kit-authoring-guide.md +64 -5
- package/docs/knowledge-kit.md +5 -1
- package/docs/learnings/2026-07-improvement-program.md +203 -0
- package/docs/migrations.md +6 -5
- package/docs/north-star.md +2 -2
- package/docs/operating-layers.md +4 -3
- package/docs/repository-structure.md +5 -5
- package/docs/skills-map.md +8 -8
- package/docs/spec/runtime-hook-surface.md +19 -15
- package/docs/spikes/graph-provider-2026-07.md +155 -0
- package/docs/standards-register.md +7 -7
- package/docs/trust-anchor-adoption.md +105 -4
- package/docs/work-item-adapters.md +1 -1
- package/docs/workflow-artifact-lifecycle.md +72 -9
- package/docs/workflow-eval-strategy.md +3 -3
- package/docs/workflow-shared-contracts.md +3 -2
- package/docs/workflow-usage-guide.md +260 -21
- package/evals/acceptance/DEMO-false-completion.md +20 -11
- package/evals/acceptance/prove-capture-teeth-declared.sh +15 -15
- package/evals/acceptance/prove-capture-teeth.sh +11 -11
- package/evals/acceptance/test_kiro_harness.sh +1 -1
- package/evals/ci/antigaming-suite.sh +5 -0
- package/evals/ci/run-baseline.sh +71 -1
- package/evals/fixtures/assignment-provider/actor-a.json +6 -0
- package/evals/fixtures/assignment-provider/actor-b.json +6 -0
- package/evals/fixtures/assignment-provider/github-issue-claimed.json +27 -0
- package/evals/fixtures/assignment-provider/github-issue-unassigned.json +7 -0
- package/evals/fixtures/assignment-provider/liveness-fresh.json +9 -0
- package/evals/fixtures/assignment-provider/liveness-stale.json +9 -0
- package/evals/fixtures/flow-kit-repository/invalid-bad-dependency/flows/review.flow.json +26 -0
- package/evals/fixtures/flow-kit-repository/invalid-bad-dependency/kit.json +13 -0
- package/evals/fixtures/flow-kit-repository/valid-with-dependency/flows/review.flow.json +26 -0
- package/evals/fixtures/flow-kit-repository/valid-with-dependency/kit.json +13 -0
- package/evals/fixtures/hook-influence/cases.json +32 -0
- package/evals/fixtures/pull-work-provider/github-issues.json +6 -6
- package/evals/fixtures/trust-reconcile-exploits/fabricated-attestation.json +59 -0
- package/evals/fixtures/trust-reconcile-exploits/no-label-bypass.json +36 -0
- package/evals/fixtures/trust-reconcile-exploits/skip-assumed-bypass.json +59 -0
- package/evals/fixtures/trust-reconcile-exploits/status-misassertion.json +48 -0
- package/evals/fixtures/trust-reconcile-exploits/waived-command-check.json +44 -0
- package/evals/fixtures/trust-reconcile-mixed-bundle/mixed-bundle.json +159 -0
- package/evals/fixtures/trust-reconcile-ws3/ws3-bundle.json +920 -0
- package/evals/integration/test_actor_identity.sh +254 -0
- package/evals/integration/test_assignment_provider_github.sh +318 -0
- package/evals/integration/test_assignment_provider_local_file.sh +222 -0
- package/evals/integration/test_builder_step_producers.sh +33 -23
- package/evals/integration/test_bundle_install.sh +22 -16
- package/evals/integration/test_bundle_lifecycle.sh +11 -11
- package/evals/integration/test_captured_fail_reconciliation.sh +50 -50
- package/evals/integration/test_checkpoint_signing.sh +43 -0
- package/evals/integration/test_claim_lookup.sh +4 -4
- package/evals/integration/test_codex_hook_resolution.sh +114 -0
- package/evals/integration/test_command_log_concurrency.sh +8 -8
- package/evals/integration/test_command_log_fork_classification.sh +3 -3
- package/evals/integration/test_command_log_integrity.sh +16 -16
- package/evals/integration/test_console_learning_projection.sh +1 -1
- package/evals/integration/test_critique_supersession_roundtrip.sh +182 -0
- package/evals/integration/test_dual_emit_flow_step.sh +145 -0
- package/evals/integration/test_enforcer_expects_driven.sh +12 -12
- package/evals/integration/test_evidence_capture_hook.sh +14 -13
- package/evals/integration/test_fixture_retirement_audit.sh +2 -2
- package/evals/integration/test_flow_agents_statusline.sh +2 -2
- package/evals/integration/test_flow_kit_install_git.sh +52 -0
- package/evals/integration/test_flow_kit_repository.sh +2 -0
- package/evals/integration/test_flowdef_session_activation.sh +6 -6
- package/evals/integration/test_flowdef_union_floor_regression.sh +429 -0
- package/evals/integration/test_gate_bypass_chain.sh +52 -40
- package/evals/integration/test_gate_lockdown.sh +97 -73
- package/evals/integration/test_gate_review_inquiry_records.sh +3 -3
- package/evals/integration/test_goal_fit_escape_hatch.sh +10 -10
- package/evals/integration/test_goal_fit_ghost_session.sh +104 -0
- package/evals/integration/test_goal_fit_hook.sh +32 -32
- package/evals/integration/test_goal_fit_rederive.sh +9 -9
- package/evals/integration/test_hook_category_behaviors.sh +103 -0
- package/evals/integration/test_hook_influence_cases.sh +1 -0
- package/evals/integration/test_install_merge.sh +262 -1
- package/evals/integration/test_kit_identity_trust.sh +6 -6
- package/evals/integration/test_liveness_conflict_injection.sh +587 -0
- package/evals/integration/test_liveness_heartbeat.sh +677 -0
- package/evals/integration/test_liveness_verdict.sh +394 -0
- package/evals/integration/test_local_flow_kit_install.sh +15 -0
- package/evals/integration/test_phase_map_and_gate_claim.sh +63 -11
- package/evals/integration/test_promote_gate.sh +178 -0
- package/evals/integration/test_publish_delivery.sh +23 -6
- package/evals/integration/test_pull_work_assignment_join.sh +132 -0
- package/evals/integration/test_pull_work_liveness_preflight.sh +394 -0
- package/evals/integration/test_pull_work_provider.sh +2 -2
- package/evals/integration/test_reconcile_soundness.sh +37 -13
- package/evals/integration/test_resolvefirststep_security.sh +22 -0
- package/evals/integration/test_runtime_adapter_activation.sh +87 -18
- package/evals/integration/test_session_resume_roundtrip.sh +147 -0
- package/evals/integration/test_sidecar_field_preservation.sh +249 -0
- package/evals/integration/test_trust_reconcile.sh +11 -10
- package/evals/integration/test_trust_reconcile_manifest.sh +79 -0
- package/evals/integration/test_trust_reconcile_mixed_bundle.sh +96 -0
- package/evals/integration/test_trust_reconcile_negatives.sh +759 -0
- package/evals/integration/test_usage_feedback_global.sh +5 -5
- package/evals/integration/test_validate_artifacts_portability.sh +60 -0
- package/evals/integration/test_verify_cli.sh +2 -2
- package/evals/integration/test_veritas_governance_kit.sh +117 -0
- package/evals/integration/test_workflow_artifact_cleanup_audit.sh +26 -1
- package/evals/integration/test_workflow_artifacts.sh +33 -0
- package/evals/integration/test_workflow_sidecar_writer.sh +802 -74
- package/evals/integration/test_workflow_steering_hook.sh +175 -5
- package/evals/lib/codex-provider.sh +1 -1
- package/evals/lib/node.sh +19 -1
- package/evals/run.sh +32 -0
- package/evals/static/test_decisions.sh +150 -0
- package/evals/static/test_flowdef_codeowners_coverage.sh +62 -0
- package/evals/static/test_knowledge_providers.sh +23 -0
- package/evals/static/test_package.sh +19 -10
- package/evals/static/test_universal_bundles.sh +48 -3
- package/evals/static/test_workflow_skills.sh +41 -8
- package/kits/builder/flows/build.flow.json +3 -74
- package/kits/builder/flows/publish-learn.flow.json +90 -0
- package/kits/builder/kit.json +11 -0
- package/kits/builder/skills/builder-shape/SKILL.md +1 -1
- package/kits/builder/skills/deliver/SKILL.md +32 -11
- package/kits/builder/skills/design-probe/SKILL.md +4 -4
- package/kits/builder/skills/evidence-gate/SKILL.md +26 -1
- package/kits/builder/skills/execute-plan/SKILL.md +2 -2
- package/kits/builder/skills/fix-bug/SKILL.md +2 -0
- package/kits/builder/skills/gate-review/SKILL.md +6 -6
- package/kits/builder/skills/idea-to-backlog/SKILL.md +1 -1
- package/kits/builder/skills/learning-review/SKILL.md +7 -7
- package/kits/builder/skills/pickup-probe/SKILL.md +5 -4
- package/kits/builder/skills/plan-work/SKILL.md +27 -5
- package/kits/builder/skills/pull-work/SKILL.md +148 -5
- package/kits/builder/skills/release-readiness/SKILL.md +4 -4
- package/kits/builder/skills/review-work/SKILL.md +24 -1
- package/kits/builder/skills/tdd-workflow/SKILL.md +2 -0
- package/kits/builder/skills/verify-work/SKILL.md +8 -1
- package/kits/catalog.json +6 -0
- package/kits/knowledge/kit.json +35 -0
- package/kits/knowledge/providers/conformance/fixtures/git-repo/CONTEXT.md +12 -0
- package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/decisions/old-sprocket-shape.md +13 -0
- package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/decisions/sprocket-shape.md +14 -0
- package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/decisions/widget-format.md +14 -0
- package/kits/knowledge/providers/conformance/fixtures/git-repo/docs/learnings/fixture-learning.md +7 -0
- package/kits/knowledge/providers/conformance/fixtures/work-item/issues.json +30 -0
- package/kits/knowledge/providers/conformance/suite.test.js +125 -0
- package/kits/knowledge/providers/git-repo/index.js +236 -0
- package/kits/knowledge/providers/health/health-pass.test.js +99 -0
- package/kits/knowledge/providers/health/index.js +153 -0
- package/kits/knowledge/providers/index.js +24 -0
- package/kits/knowledge/providers/lib/model.js +91 -0
- package/kits/knowledge/providers/lib/schema-validate.js +119 -0
- package/kits/knowledge/providers/markdown-vault/index.js +169 -0
- package/kits/knowledge/providers/work-item/index.js +204 -0
- package/kits/veritas-governance/adapter/readiness-to-trust-bundle.mjs +178 -0
- package/kits/veritas-governance/docs/README.md +75 -0
- package/kits/veritas-governance/fixtures/readiness/not-ready.readiness-report.json +4645 -0
- package/kits/veritas-governance/fixtures/readiness/ready.readiness-report.json +4403 -0
- package/kits/veritas-governance/flows/readiness-check.flow.json +35 -0
- package/kits/veritas-governance/kit.json +16 -0
- package/package.json +11 -5
- package/packaging/README.md +2 -2
- package/packaging/conformance/fixtures/config-protection--allow-read-state-json.json +20 -0
- package/packaging/conformance/fixtures/config-protection--block-write-state-json.json +20 -0
- package/packaging/conformance/fixtures/stop-goal-fit--block-bundle-disputed-claim.json +1 -1
- package/packaging/conformance/fixtures/stop-goal-fit--block-capture-contradicts-claimed-pass.json +1 -1
- package/packaging/manifest.json +9 -10
- package/schemas/assignment-provider-settings.schema.json +125 -0
- package/schemas/decision-record.schema.json +109 -0
- package/schemas/knowledge/edge.schema.json +54 -0
- package/schemas/knowledge/health-report.schema.json +45 -0
- package/schemas/knowledge/node.schema.json +49 -0
- package/schemas/knowledge/proposal.schema.json +53 -0
- package/schemas/workflow-evidence.schema.json +1 -1
- package/schemas/workflow-state.schema.json +5 -0
- package/scripts/README.md +8 -3
- package/scripts/check-content-boundary.cjs +1 -1
- package/scripts/check-decisions.cjs +356 -0
- package/scripts/ci/derive-claim-status.mjs +74 -0
- package/scripts/ci/trust-reconcile.js +957 -103
- package/scripts/git-status.sh +2 -2
- package/scripts/hooks/claude-telemetry-hook.js +37 -3
- package/scripts/hooks/codex-telemetry-hook.js +36 -2
- package/scripts/hooks/config-protection.js +102 -30
- package/scripts/hooks/evidence-capture.js +3 -3
- package/scripts/hooks/lib/actor-identity.js +368 -0
- package/scripts/hooks/lib/config-protection-remedies.js +69 -0
- package/scripts/hooks/lib/liveness-heartbeat.js +361 -0
- package/scripts/hooks/lib/liveness-policy.js +127 -0
- package/scripts/hooks/lib/liveness-read.js +105 -12
- package/scripts/hooks/lib/liveness-write.js +46 -0
- package/scripts/hooks/lib/local-artifact-paths.js +12 -6
- package/scripts/hooks/opencode-telemetry-hook.js +27 -1
- package/scripts/hooks/pi-telemetry-hook.js +27 -1
- package/scripts/hooks/stop-goal-fit.js +157 -26
- package/scripts/hooks/workflow-steering.js +77 -3
- package/scripts/install-codex-home.sh +138 -38
- package/scripts/statusline/flow-agents-statusline.js +2 -2
- package/src/cli/assignment-provider.ts +845 -0
- package/src/cli/console-learning-projection.ts +3 -2
- package/src/cli/effective-assignment-provider-settings.ts +112 -0
- package/src/cli/init.ts +101 -21
- package/src/cli/kit.ts +37 -6
- package/src/cli/public-api.test.mjs +58 -6
- package/src/cli/pull-work-provider.ts +1 -1
- package/src/cli/trust-bundle-policy-order.test.mjs +87 -0
- package/src/cli/usage-feedback.ts +3 -3
- package/src/cli/validate-hook-influence.ts +1 -0
- package/src/cli/validate-workflow-artifacts.ts +27 -7
- package/src/cli/veritas-readiness-adapter.test.mjs +267 -0
- package/src/cli/workflow-artifact-cleanup-audit.ts +35 -2
- package/src/cli/workflow-sidecar.ts +933 -168
- package/src/cli.ts +6 -0
- package/src/flow-kit/validate.ts +74 -1
- package/src/index.ts +6 -2
- package/src/lib/flow-resolver.ts +123 -55
- package/src/lib/local-artifact-root.ts +24 -5
- package/src/lib/workflow-learning-projection.ts +2 -2
- package/src/runtime-adapters.ts +61 -15
- package/src/tools/build-universal-bundles.ts +49 -16
- package/src/tools/generate-context-map.ts +10 -8
- package/src/tools/validate-source-tree.ts +24 -1
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://flow-agents.dev/schemas/knowledge/node.schema.json",
|
|
4
|
+
"title": "Knowledge Graph Node",
|
|
5
|
+
"description": "A typed node in the Knowledge Kit graph model. type is EXTENSIBLE: the recommended core vocabulary is note, decision, issue, session, person, but providers MAY emit additional types. Every node carries provenance so any assertion can be traced back to the store it came from.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["id", "type", "title", "provenance"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"id": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"minLength": 1,
|
|
13
|
+
"description": "Provider-scoped stable identifier, unique within a single readGraph result."
|
|
14
|
+
},
|
|
15
|
+
"type": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"minLength": 1,
|
|
18
|
+
"description": "Node type. Recommended core vocabulary: note, decision, issue, session, person. Extensible — unknown types MUST NOT be rejected."
|
|
19
|
+
},
|
|
20
|
+
"title": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"minLength": 1,
|
|
23
|
+
"description": "Human-readable title used for display and duplicate detection."
|
|
24
|
+
},
|
|
25
|
+
"body": {
|
|
26
|
+
"type": "string",
|
|
27
|
+
"description": "Primary content. Format is provider/type-specific."
|
|
28
|
+
},
|
|
29
|
+
"attributes": {
|
|
30
|
+
"type": "object",
|
|
31
|
+
"description": "Provider-specific structured fields (e.g. issue state/labels, decision status, note category/tags)."
|
|
32
|
+
},
|
|
33
|
+
"provenance": { "$ref": "#/$defs/provenance" }
|
|
34
|
+
},
|
|
35
|
+
"$defs": {
|
|
36
|
+
"provenance": {
|
|
37
|
+
"type": "object",
|
|
38
|
+
"additionalProperties": false,
|
|
39
|
+
"required": ["provider", "source", "retrieved_at"],
|
|
40
|
+
"properties": {
|
|
41
|
+
"provider": { "type": "string", "minLength": 1, "description": "Provider id that produced this element (e.g. markdown-vault, git-repo, work-item)." },
|
|
42
|
+
"source": { "type": "string", "minLength": 1, "description": "The concrete origin: a file path, an issue ref, or a URL." },
|
|
43
|
+
"locator": { "type": "string", "description": "Optional finer locator within the source (line, anchor, frontmatter field)." },
|
|
44
|
+
"retrieved_at": { "type": "string", "minLength": 1, "description": "ISO-8601 timestamp the element was read." },
|
|
45
|
+
"agent": { "type": "string", "description": "Optional agent/process id that performed the read." }
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://flow-agents.dev/schemas/knowledge/proposal.schema.json",
|
|
4
|
+
"title": "Knowledge Store Write Proposal",
|
|
5
|
+
"description": "The ONLY write artifact a knowledge store provider may emit. Providers over human-curated stores are proposals-only: proposeWrite() never mutates the store and can only ever return a Proposal whose status is the const \"proposed\". Enactment happens downstream (a human edit, the decision-registry promote step, or a gh comment/label the operator files) — never inside the provider. The `rendered` field carries the provider-native form the enactor would apply (decision-registry topic frontmatter for git-repo; an issue comment/label body for work-item; a vault note with frontmatter+wikilinks for markdown-vault).",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["schema_version", "provider", "kind", "target", "status", "provenance"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"schema_version": { "const": "1.0" },
|
|
11
|
+
"provider": { "type": "string", "minLength": 1 },
|
|
12
|
+
"kind": {
|
|
13
|
+
"type": "string",
|
|
14
|
+
"enum": ["create-node", "update-node", "add-edge", "comment", "label", "decision-topic"],
|
|
15
|
+
"description": "The shape of the proposed change."
|
|
16
|
+
},
|
|
17
|
+
"target": {
|
|
18
|
+
"type": "object",
|
|
19
|
+
"description": "Where the change would land if enacted (file path, issue number, store id). Descriptive only — the provider does not touch it."
|
|
20
|
+
},
|
|
21
|
+
"payload": {
|
|
22
|
+
"type": "object",
|
|
23
|
+
"description": "The structured node/edge/field the proposal would introduce."
|
|
24
|
+
},
|
|
25
|
+
"rendered": {
|
|
26
|
+
"type": "string",
|
|
27
|
+
"description": "The provider-native rendered form an enactor would apply (topic-file frontmatter, gh comment body, vault note)."
|
|
28
|
+
},
|
|
29
|
+
"status": {
|
|
30
|
+
"const": "proposed",
|
|
31
|
+
"description": "Always the literal \"proposed\". A provider can NEVER return an applied proposal — this const is the structural expression of proposals-only discipline."
|
|
32
|
+
},
|
|
33
|
+
"rationale": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"description": "Why the change is proposed."
|
|
36
|
+
},
|
|
37
|
+
"provenance": { "$ref": "#/$defs/provenance" }
|
|
38
|
+
},
|
|
39
|
+
"$defs": {
|
|
40
|
+
"provenance": {
|
|
41
|
+
"type": "object",
|
|
42
|
+
"additionalProperties": false,
|
|
43
|
+
"required": ["provider", "source", "retrieved_at"],
|
|
44
|
+
"properties": {
|
|
45
|
+
"provider": { "type": "string", "minLength": 1 },
|
|
46
|
+
"source": { "type": "string", "minLength": 1 },
|
|
47
|
+
"locator": { "type": "string" },
|
|
48
|
+
"retrieved_at": { "type": "string", "minLength": 1 },
|
|
49
|
+
"agent": { "type": "string" }
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -174,7 +174,7 @@
|
|
|
174
174
|
},
|
|
175
175
|
"kind": {
|
|
176
176
|
"type": "string",
|
|
177
|
-
"enum": ["build", "types", "lint", "test", "security", "diff", "browser", "runtime", "policy", "external"]
|
|
177
|
+
"enum": ["build", "types", "lint", "test", "command", "security", "diff", "browser", "runtime", "policy", "external"]
|
|
178
178
|
},
|
|
179
179
|
"status": {
|
|
180
180
|
"type": "string",
|
|
@@ -36,6 +36,11 @@
|
|
|
36
36
|
"owner": {
|
|
37
37
|
"type": "string"
|
|
38
38
|
},
|
|
39
|
+
"branch": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"minLength": 1,
|
|
42
|
+
"description": "Routing branch for this session, e.g. agent/<actor>/<slug> (ADR 0021 \u00a73/\u00a75, issue #289). Optional for migration honesty: legacy/in-flight sessions predate this field. ensure-session always writes it for newly created sessions; validate-workflow-artifacts warns (does not fail) when it is absent."
|
|
43
|
+
},
|
|
39
44
|
"created_at": {
|
|
40
45
|
"type": "string",
|
|
41
46
|
"format": "date-time"
|
package/scripts/README.md
CHANGED
|
@@ -52,7 +52,7 @@ renamed, or changes category, update the table and the validator together.
|
|
|
52
52
|
| `codex-telemetry-hook.js` | telemetry shim | `evals/integration/test_hook_category_behaviors.sh`, `evals/integration/test_telemetry.sh` | Captures Codex hook telemetry and fails open. |
|
|
53
53
|
| `run-hook.js` | hook runner | `evals/integration/test_hook_category_behaviors.sh`, `evals/integration/test_goal_fit_hook.sh`, `evals/integration/test_workflow_steering_hook.sh` | Applies profile/disable flags, traversal checks, and hook execution. |
|
|
54
54
|
| `config-protection.js` | policy hook | `evals/integration/test_hook_category_behaviors.sh` | Blocks unsafe runtime config edits. |
|
|
55
|
-
| `evidence-capture.js` | policy hook | `evals/integration/test_evidence_capture_hook.sh` | Deterministically captures command executions to `.flow-agents/<slug>/command-log.jsonl` so evidence is machine-recorded, not model-claimed (cross-referenced by stop-goal-fit). |
|
|
55
|
+
| `evidence-capture.js` | policy hook | `evals/integration/test_evidence_capture_hook.sh` | Deterministically captures command executions to `.kontourai/flow-agents/<slug>/command-log.jsonl` so evidence is machine-recorded, not model-claimed (cross-referenced by stop-goal-fit). |
|
|
56
56
|
| `governance-audit.sh` | policy hook | `evals/integration/test_hook_category_behaviors.sh`, `evals/integration/test_telemetry.sh` | Emits governance/Veritas audit context when configured. |
|
|
57
57
|
| `opencode-hook-adapter.js` | runtime adapter | `evals/integration/test_bundle_install.sh` | Translates opencode plugin events into the shared hook runner contract. |
|
|
58
58
|
| `opencode-telemetry-hook.js` | telemetry shim | `evals/integration/test_bundle_install.sh` | Captures opencode plugin telemetry and fails open. |
|
|
@@ -67,10 +67,15 @@ renamed, or changes category, update the table and the validator together.
|
|
|
67
67
|
| `workflow-steering.js` | policy hook | `evals/integration/test_workflow_steering_hook.sh` | Provides workflow guidance from current artifact state. |
|
|
68
68
|
| `pre-commit-quality.js` | repo guardrail hook | `evals/integration/test_hook_category_behaviors.sh` | Supports repository Git hook checks, not installed runtime hooks. |
|
|
69
69
|
| `desktop-notify.sh` | local notification helper | `evals/integration/test_hook_category_behaviors.sh` | Optional local desktop notification helper. |
|
|
70
|
+
| `lib/actor-identity.js` | shared hook library | `evals/integration/test_actor_identity.sh` | Shared runtime-agnostic actor identity resolver (`resolveActor`) consumed by `workflow-steering.js` and `workflow-sidecar.js` to retire the shared `"local"` liveness-actor default (issue #287). |
|
|
70
71
|
| `lib/audit-transport.sh` | shared hook library | `evals/integration/test_hook_category_behaviors.sh`, `evals/integration/test_telemetry.sh` | Shared audit event transport functions. |
|
|
72
|
+
| `lib/config-protection-remedies.js` | shared hook library | `evals/integration/test_hook_category_behaviors.sh` | Sanctioned-remedy table for `config-protection.js` (WS8/ADR 0020 extraction). |
|
|
71
73
|
| `lib/hook-flags.js` | shared hook library | `evals/integration/test_hook_category_behaviors.sh` | Shared profile/disable flag parsing. |
|
|
74
|
+
| `lib/liveness-heartbeat.js` | shared hook library | `evals/integration/test_liveness_heartbeat.sh` | Shared tool-activity liveness heartbeat (`maybeEmitHeartbeat`); rides `postToolUse` across all four telemetry hook wrappers, throttled and fail-open (issue #288). |
|
|
75
|
+
| `lib/liveness-policy.js` | shared hook library | `evals/integration/test_workflow_sidecar_writer.sh`, `evals/integration/test_liveness_heartbeat.sh` | Shared liveness on/off predicate (`isLivenessEnabled`, default-on/opt-out) and TTL/heartbeat-throttle default resolution (issue #288). |
|
|
72
76
|
| `lib/liveness-read.js` | shared hook library | `evals/integration/test_session_resume_roundtrip.sh` | Shared liveness event reader + freshness check (`readLivenessEvents`, `freshHolders`); consumed by the reground hook and `workflow-sidecar liveness status`. |
|
|
73
|
-
| `lib/
|
|
77
|
+
| `lib/liveness-write.js` | shared hook library | `evals/integration/test_workflow_sidecar_writer.sh`, `evals/integration/test_liveness_heartbeat.sh` | Shared liveness event stream writer (`livenessStreamFile`, `appendLivenessEvent`), lifted from `workflow-sidecar.ts` so the CLI and hook wrappers share one writer (issue #288). |
|
|
78
|
+
| `lib/local-artifact-paths.js` | shared hook library | `evals/integration/test_migrate_local_artifacts.sh`, `evals/integration/test_workflow_sidecar_writer.sh` | Shared `.kontourai/flow-agents` artifact-root helpers for CJS hooks. |
|
|
74
79
|
| `lib/patterns.sh` | shared hook library | `evals/integration/test_hook_category_behaviors.sh`, `evals/integration/test_telemetry.sh` | Shared shell pattern constants. |
|
|
75
80
|
| `lib/resolve-formatter.js` | shared hook library | `evals/integration/test_hook_category_behaviors.sh` | Shared formatter resolution helper. |
|
|
76
81
|
|
|
@@ -103,7 +108,7 @@ prompting. Add `--allow-network` to probe a non-local HTTPS Console endpoint.
|
|
|
103
108
|
|
|
104
109
|
## Install And Repo Utilities
|
|
105
110
|
|
|
106
|
-
- `install-codex-home.sh`: installs the
|
|
111
|
+
- `install-codex-home.sh`: installs the generated Codex bundle into `CODEX_HOME`, or `~/.codex` when `CODEX_HOME` is unset. A positional destination argument is the explicit override for isolated homes and tests.
|
|
107
112
|
- `setup-repo-hooks.sh`: configures this clone's Git hook path.
|
|
108
113
|
- `check-content-boundary.cjs`, `detect-tools.sh`, `discover-agents.sh`, `git-status.sh`: repo-local helper commands.
|
|
109
114
|
- `context-budget/` and `statusline/`: specialized support tooling copied into bundles where needed.
|
|
@@ -42,7 +42,7 @@ function isIgnoredPath(filePath) {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
function isWorkflowRuntimeArtifact(filePath) {
|
|
45
|
-
return filePath.startsWith(".flow-agents/");
|
|
45
|
+
return filePath.startsWith(".kontourai/flow-agents/") || filePath.startsWith(".flow-agents/");
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
function lineNumberFor(content, index) {
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
// check-decisions.cjs — validator + index generator for the topic-keyed
|
|
5
|
+
// decision registry (context/contracts/decision-registry-contract.md).
|
|
6
|
+
//
|
|
7
|
+
// Usage:
|
|
8
|
+
// node scripts/check-decisions.cjs check # validate all topic files + assert index is current (default)
|
|
9
|
+
// node scripts/check-decisions.cjs gen-index # (re)write docs/decisions/index.md deterministically
|
|
10
|
+
//
|
|
11
|
+
// Zero runtime dependencies (mirrors scripts/check-content-boundary.cjs): the
|
|
12
|
+
// decision-record JSON schema at schemas/decision-record.schema.json is the
|
|
13
|
+
// normative structural contract; this script enforces the same rules directly
|
|
14
|
+
// so it runs in `npm ci` environments without a JSON-schema/YAML library.
|
|
15
|
+
|
|
16
|
+
const fs = require("node:fs");
|
|
17
|
+
const path = require("node:path");
|
|
18
|
+
|
|
19
|
+
const ROOT = path.resolve(__dirname, "..");
|
|
20
|
+
// FLOW_AGENTS_DECISIONS_DIR lets the eval suite point the validator/generator at
|
|
21
|
+
// a throwaway fixture directory (mirrors FLOW_AGENTS_CONTENT_BOUNDARY_FILES).
|
|
22
|
+
const DECISIONS_DIR = process.env.FLOW_AGENTS_DECISIONS_DIR
|
|
23
|
+
? path.resolve(process.env.FLOW_AGENTS_DECISIONS_DIR)
|
|
24
|
+
: path.join(ROOT, "docs", "decisions");
|
|
25
|
+
const INDEX_PATH = path.join(DECISIONS_DIR, "index.md");
|
|
26
|
+
const INDEX_SLUG = "index";
|
|
27
|
+
|
|
28
|
+
const STATUSES = ["current", "superseded", "merged", "needs-decision"];
|
|
29
|
+
const EVIDENCE_KINDS = ["issue", "pr", "commit", "session-archive", "adr", "doc", "url"];
|
|
30
|
+
const SLUG_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
31
|
+
const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
32
|
+
|
|
33
|
+
// Secret-shaped literal detectors — mirror scripts/hooks/lib/patterns.sh so the
|
|
34
|
+
// registry never captures a credential in an evidence ref.
|
|
35
|
+
const SECRET_PATTERNS = [
|
|
36
|
+
{ label: "aws access key id", re: /AKIA[A-Z0-9]{16}/ },
|
|
37
|
+
{ label: "aws sts key id", re: /ASIA[A-Z0-9]{16}/ },
|
|
38
|
+
{ label: "github token", re: /gh[pousr]_[A-Za-z0-9_]{36,}/ },
|
|
39
|
+
{ label: "private key block", re: /BEGIN[A-Z ]*PRIVATE KEY/ },
|
|
40
|
+
{ label: "jwt", re: /eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/ },
|
|
41
|
+
{
|
|
42
|
+
label: "generic secret literal",
|
|
43
|
+
re: /(secret|password|token|api[_-]?key)\s*[:=]\s*["'][^"']{8,}/i,
|
|
44
|
+
},
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
function listTopicFiles() {
|
|
48
|
+
if (!fs.existsSync(DECISIONS_DIR)) return [];
|
|
49
|
+
return fs
|
|
50
|
+
.readdirSync(DECISIONS_DIR)
|
|
51
|
+
.filter((name) => name.endsWith(".md") && name !== "index.md")
|
|
52
|
+
.sort();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Minimal, strict YAML-frontmatter reader. The decision-record frontmatter is a
|
|
56
|
+
// flat map of scalars plus two list shapes (evidence[] as `- kind: .. ` blocks
|
|
57
|
+
// and slug arrays as inline `[a, b]` or block `- slug`). We parse exactly that
|
|
58
|
+
// shape and reject anything we do not understand rather than guessing.
|
|
59
|
+
function parseFrontmatter(raw, errors) {
|
|
60
|
+
if (!raw.startsWith("---\n") && !raw.startsWith("---\r\n")) {
|
|
61
|
+
errors.push("file must begin with a YAML frontmatter block delimited by '---'");
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
const lines = raw.split(/\r?\n/);
|
|
65
|
+
let end = -1;
|
|
66
|
+
for (let i = 1; i < lines.length; i += 1) {
|
|
67
|
+
if (lines[i].trim() === "---") {
|
|
68
|
+
end = i;
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (end === -1) {
|
|
73
|
+
errors.push("frontmatter block is not terminated by a closing '---'");
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
const body = lines.slice(1, end);
|
|
77
|
+
const data = {};
|
|
78
|
+
let cursorKey = null;
|
|
79
|
+
|
|
80
|
+
for (let i = 0; i < body.length; i += 1) {
|
|
81
|
+
const line = body[i];
|
|
82
|
+
if (line.trim() === "" || line.trim().startsWith("#")) continue;
|
|
83
|
+
|
|
84
|
+
const topMatch = line.match(/^([A-Za-z0-9_]+):\s*(.*)$/);
|
|
85
|
+
const listItem = line.match(/^\s*-\s+(.*)$/);
|
|
86
|
+
|
|
87
|
+
if (topMatch && !line.startsWith(" ")) {
|
|
88
|
+
const key = topMatch[1];
|
|
89
|
+
const rest = topMatch[2].trim();
|
|
90
|
+
cursorKey = key;
|
|
91
|
+
if (rest === "") {
|
|
92
|
+
data[key] = { __pending_list: true, items: [] };
|
|
93
|
+
} else if (rest.startsWith("[") && rest.endsWith("]")) {
|
|
94
|
+
const inner = rest.slice(1, -1).trim();
|
|
95
|
+
data[key] = inner === "" ? [] : inner.split(",").map((s) => stripScalar(s.trim()));
|
|
96
|
+
} else {
|
|
97
|
+
data[key] = stripScalar(rest);
|
|
98
|
+
}
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (listItem && cursorKey) {
|
|
103
|
+
const container = data[cursorKey];
|
|
104
|
+
if (!container || !container.__pending_list) {
|
|
105
|
+
errors.push(`unexpected list item under '${cursorKey}': ${line.trim()}`);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const itemBody = listItem[1].trim();
|
|
109
|
+
// Object list item: `- kind: pr, ref: ...` OR `- kind: pr` then following ` ref: ...`
|
|
110
|
+
const inlinePair = itemBody.match(/^([A-Za-z0-9_]+):\s*(.*)$/);
|
|
111
|
+
if (inlinePair) {
|
|
112
|
+
const obj = {};
|
|
113
|
+
obj[inlinePair[1]] = stripScalar(inlinePair[2].trim());
|
|
114
|
+
// consume subsequent indented `key: value` continuation lines
|
|
115
|
+
while (i + 1 < body.length && /^\s+[A-Za-z0-9_]+:\s*/.test(body[i + 1]) && !/^\s*-\s+/.test(body[i + 1])) {
|
|
116
|
+
const cont = body[i + 1].trim().match(/^([A-Za-z0-9_]+):\s*(.*)$/);
|
|
117
|
+
if (!cont) break;
|
|
118
|
+
obj[cont[1]] = stripScalar(cont[2].trim());
|
|
119
|
+
i += 1;
|
|
120
|
+
}
|
|
121
|
+
container.items.push(obj);
|
|
122
|
+
} else {
|
|
123
|
+
container.items.push(stripScalar(itemBody));
|
|
124
|
+
}
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
errors.push(`unparseable frontmatter line: ${line}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Normalize pending lists to plain arrays.
|
|
132
|
+
for (const key of Object.keys(data)) {
|
|
133
|
+
if (data[key] && data[key].__pending_list) {
|
|
134
|
+
data[key] = data[key].items;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return data;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function stripScalar(value) {
|
|
141
|
+
let v = value.trim();
|
|
142
|
+
if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
|
|
143
|
+
v = v.slice(1, -1);
|
|
144
|
+
}
|
|
145
|
+
return v;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function firstBodyLine(raw) {
|
|
149
|
+
const lines = raw.split(/\r?\n/);
|
|
150
|
+
let seenClose = false;
|
|
151
|
+
let count = 0;
|
|
152
|
+
for (const line of lines) {
|
|
153
|
+
if (line.trim() === "---") {
|
|
154
|
+
count += 1;
|
|
155
|
+
if (count === 2) seenClose = true;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (!seenClose) continue;
|
|
159
|
+
const t = line.trim();
|
|
160
|
+
if (t === "") continue;
|
|
161
|
+
return t.replace(/^#+\s*/, "").trim();
|
|
162
|
+
}
|
|
163
|
+
return "";
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function validateFile(slug, raw, knownSlugs) {
|
|
167
|
+
const errors = [];
|
|
168
|
+
const fm = parseFrontmatter(raw, errors);
|
|
169
|
+
if (!fm) return errors;
|
|
170
|
+
|
|
171
|
+
if (!SLUG_RE.test(slug)) {
|
|
172
|
+
errors.push(`filename slug '${slug}' is not a valid kebab-case topic slug`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// status
|
|
176
|
+
if (!("status" in fm)) {
|
|
177
|
+
errors.push("missing required field: status");
|
|
178
|
+
} else if (!STATUSES.includes(fm.status)) {
|
|
179
|
+
errors.push(`unknown status '${fm.status}' (allowed: ${STATUSES.join(", ")})`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// subject
|
|
183
|
+
if (!("subject" in fm) || typeof fm.subject !== "string" || fm.subject.trim() === "") {
|
|
184
|
+
errors.push("missing required field: subject (non-empty)");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// decided
|
|
188
|
+
if (!("decided" in fm)) {
|
|
189
|
+
errors.push("missing required field: decided");
|
|
190
|
+
} else if (typeof fm.decided !== "string" || !DATE_RE.test(fm.decided)) {
|
|
191
|
+
errors.push(`decided must be an ISO date (YYYY-MM-DD); got '${fm.decided}'`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// evidence
|
|
195
|
+
if (!("evidence" in fm)) {
|
|
196
|
+
errors.push("missing required field: evidence[]");
|
|
197
|
+
} else if (!Array.isArray(fm.evidence) || fm.evidence.length === 0) {
|
|
198
|
+
errors.push("evidence must be a non-empty array of {kind, ref}");
|
|
199
|
+
} else {
|
|
200
|
+
fm.evidence.forEach((item, idx) => {
|
|
201
|
+
if (typeof item !== "object" || item === null || Array.isArray(item)) {
|
|
202
|
+
errors.push(`evidence[${idx}] must be an object {kind, ref}`);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (!EVIDENCE_KINDS.includes(item.kind)) {
|
|
206
|
+
errors.push(`evidence[${idx}].kind '${item.kind}' is not one of ${EVIDENCE_KINDS.join(", ")}`);
|
|
207
|
+
}
|
|
208
|
+
if (typeof item.ref !== "string" || item.ref.trim() === "") {
|
|
209
|
+
errors.push(`evidence[${idx}].ref must be a non-empty string`);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
for (const pat of SECRET_PATTERNS) {
|
|
213
|
+
if (pat.re.test(item.ref)) {
|
|
214
|
+
errors.push(`evidence[${idx}].ref contains a secret-shaped literal (${pat.label}); link durable provenance, never a credential`);
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// tombstone / relationship fields
|
|
222
|
+
const relSingles = ["superseded_by", "merged_into"];
|
|
223
|
+
for (const key of relSingles) {
|
|
224
|
+
if (key in fm) {
|
|
225
|
+
const target = fm[key];
|
|
226
|
+
if (typeof target !== "string" || !SLUG_RE.test(target)) {
|
|
227
|
+
errors.push(`${key} must be a single topic slug; got '${target}'`);
|
|
228
|
+
} else if (!knownSlugs.has(target)) {
|
|
229
|
+
errors.push(`${key} points at missing topic slug '${target}' (no docs/decisions/${target}.md)`);
|
|
230
|
+
} else if (target === slug) {
|
|
231
|
+
errors.push(`${key} must not point at its own slug`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if ("supersedes" in fm) {
|
|
236
|
+
const arr = Array.isArray(fm.supersedes) ? fm.supersedes : [fm.supersedes];
|
|
237
|
+
arr.forEach((target) => {
|
|
238
|
+
if (typeof target !== "string" || !SLUG_RE.test(target)) {
|
|
239
|
+
errors.push(`supersedes entry must be a topic slug; got '${target}'`);
|
|
240
|
+
} else if (!knownSlugs.has(target)) {
|
|
241
|
+
errors.push(`supersedes points at missing topic slug '${target}'`);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// status-conditional relationship rules
|
|
247
|
+
if (fm.status === "current" || fm.status === "needs-decision") {
|
|
248
|
+
if ("superseded_by" in fm || "merged_into" in fm) {
|
|
249
|
+
errors.push(`status '${fm.status}' must not carry superseded_by or merged_into`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (fm.status === "superseded") {
|
|
253
|
+
if (!("superseded_by" in fm)) errors.push("status 'superseded' requires superseded_by");
|
|
254
|
+
if ("merged_into" in fm) errors.push("status 'superseded' must not carry merged_into");
|
|
255
|
+
}
|
|
256
|
+
if (fm.status === "merged") {
|
|
257
|
+
if (!("merged_into" in fm)) errors.push("status 'merged' requires merged_into");
|
|
258
|
+
if ("superseded_by" in fm) errors.push("status 'merged' must not carry superseded_by");
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return errors;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function readTopics() {
|
|
265
|
+
const files = listTopicFiles();
|
|
266
|
+
const knownSlugs = new Set(files.map((f) => f.replace(/\.md$/, "")));
|
|
267
|
+
return files.map((file) => {
|
|
268
|
+
const slug = file.replace(/\.md$/, "");
|
|
269
|
+
const raw = fs.readFileSync(path.join(DECISIONS_DIR, file), "utf8");
|
|
270
|
+
const errors = validateFile(slug, raw, knownSlugs);
|
|
271
|
+
const fm = parseFrontmatter(raw, []);
|
|
272
|
+
return { slug, raw, fm: fm || {}, errors };
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function oneLiner(topic) {
|
|
277
|
+
const subject = topic.fm && typeof topic.fm.subject === "string" ? topic.fm.subject.trim() : "";
|
|
278
|
+
if (subject) return subject;
|
|
279
|
+
const body = firstBodyLine(topic.raw);
|
|
280
|
+
return body || topic.slug;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function renderIndex(topics) {
|
|
284
|
+
const lines = [];
|
|
285
|
+
lines.push("---");
|
|
286
|
+
lines.push("title: Decision Index");
|
|
287
|
+
lines.push("---");
|
|
288
|
+
lines.push("");
|
|
289
|
+
lines.push("# Decision Index");
|
|
290
|
+
lines.push("");
|
|
291
|
+
lines.push("Generated by `npm run gen:decisions-index`. Do not edit by hand.");
|
|
292
|
+
lines.push("Topic-keyed living decision records per `context/contracts/decision-registry-contract.md`.");
|
|
293
|
+
lines.push("Numbered ADRs under `docs/adr/` are frozen history and are not listed here.");
|
|
294
|
+
lines.push("");
|
|
295
|
+
lines.push("| Topic | Status | Decision |");
|
|
296
|
+
lines.push("| --- | --- | --- |");
|
|
297
|
+
const sorted = [...topics].sort((a, b) => a.slug.localeCompare(b.slug));
|
|
298
|
+
for (const topic of sorted) {
|
|
299
|
+
const status = topic.fm && typeof topic.fm.status === "string" ? topic.fm.status : "unknown";
|
|
300
|
+
const summary = oneLiner(topic).replace(/\|/g, "\\|");
|
|
301
|
+
lines.push(`| [${topic.slug}](./${topic.slug}.md) | ${status} | ${summary} |`);
|
|
302
|
+
}
|
|
303
|
+
lines.push("");
|
|
304
|
+
return lines.join("\n");
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function cmdGenIndex() {
|
|
308
|
+
fs.mkdirSync(DECISIONS_DIR, { recursive: true });
|
|
309
|
+
const topics = readTopics();
|
|
310
|
+
const content = renderIndex(topics);
|
|
311
|
+
fs.writeFileSync(INDEX_PATH, content, "utf8");
|
|
312
|
+
console.log(`Wrote ${path.relative(ROOT, INDEX_PATH)} (${topics.length} topic${topics.length === 1 ? "" : "s"}).`);
|
|
313
|
+
return 0;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function cmdCheck() {
|
|
317
|
+
const topics = readTopics();
|
|
318
|
+
let failed = false;
|
|
319
|
+
|
|
320
|
+
for (const topic of topics) {
|
|
321
|
+
if (topic.errors.length > 0) {
|
|
322
|
+
failed = true;
|
|
323
|
+
console.error(`FAIL docs/decisions/${topic.slug}.md`);
|
|
324
|
+
for (const err of topic.errors) console.error(` - ${err}`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Index freshness: check regenerates deterministically and must match on disk
|
|
329
|
+
// (this is what makes regeneration idempotent + diff-clean, AC2).
|
|
330
|
+
const expectedIndex = renderIndex(topics);
|
|
331
|
+
const actualIndex = fs.existsSync(INDEX_PATH) ? fs.readFileSync(INDEX_PATH, "utf8") : null;
|
|
332
|
+
if (actualIndex === null) {
|
|
333
|
+
failed = true;
|
|
334
|
+
console.error("FAIL docs/decisions/index.md is missing; run `npm run gen:decisions-index`");
|
|
335
|
+
} else if (actualIndex !== expectedIndex) {
|
|
336
|
+
failed = true;
|
|
337
|
+
console.error("FAIL docs/decisions/index.md is stale; run `npm run gen:decisions-index`");
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (failed) {
|
|
341
|
+
console.error("Decision registry check failed.");
|
|
342
|
+
return 1;
|
|
343
|
+
}
|
|
344
|
+
console.log(`Decision registry check passed (${topics.length} topic${topics.length === 1 ? "" : "s"}).`);
|
|
345
|
+
return 0;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function main() {
|
|
349
|
+
const mode = process.argv[2] || "check";
|
|
350
|
+
if (mode === "check") return process.exit(cmdCheck());
|
|
351
|
+
if (mode === "gen-index") return process.exit(cmdGenIndex());
|
|
352
|
+
console.error(`Unknown mode '${mode}'. Usage: check-decisions.cjs [check|gen-index]`);
|
|
353
|
+
return process.exit(2);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
main();
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// derive-claim-status.mjs — WS8 (AC1/finding-3) CI-side status re-derivation helper.
|
|
3
|
+
//
|
|
4
|
+
// The trust reconciler MUST NOT trust a bundle's self-reported `claim.status`. This helper
|
|
5
|
+
// re-derives each claim's status from the bundle's OWN evidence/events/policies using
|
|
6
|
+
// @kontourai/surface's canonical `deriveClaimStatus` — the exact function the producer used
|
|
7
|
+
// — so the reconciler can detect a status-misassertion (asserted status != derived status).
|
|
8
|
+
//
|
|
9
|
+
// It is a separate ESM module because @kontourai/surface is ESM-only while
|
|
10
|
+
// scripts/ci/trust-reconcile.js is CommonJS with a synchronous entrypoint; the reconciler
|
|
11
|
+
// invokes this via spawnSync and parses the JSON on stdout. Surface is resolved from THIS
|
|
12
|
+
// file's location (the reconciler's own node_modules), NOT the target repo-root, so an
|
|
13
|
+
// adopter repo without Surface still gets CI-side re-derivation from the anchor's copy.
|
|
14
|
+
//
|
|
15
|
+
// Usage: node derive-claim-status.mjs <bundle-path>
|
|
16
|
+
// Output (stdout): {"claimId": "<TrustStatus>", ...} — value is null if that claim threw.
|
|
17
|
+
// Exit: 0 on success (Surface loaded); 2 if Surface is unavailable; 3 on bad input.
|
|
18
|
+
|
|
19
|
+
import { readFileSync } from "node:fs";
|
|
20
|
+
|
|
21
|
+
async function main() {
|
|
22
|
+
const bundlePath = process.argv[2];
|
|
23
|
+
if (!bundlePath) { process.stderr.write("derive-claim-status: bundle path argument required\n"); return 3; }
|
|
24
|
+
|
|
25
|
+
let surface;
|
|
26
|
+
try {
|
|
27
|
+
surface = await import("@kontourai/surface");
|
|
28
|
+
} catch (err) {
|
|
29
|
+
process.stderr.write(`derive-claim-status: @kontourai/surface unavailable: ${err && err.message ? err.message : String(err)}\n`);
|
|
30
|
+
return 2;
|
|
31
|
+
}
|
|
32
|
+
const { deriveClaimStatus } = surface;
|
|
33
|
+
if (typeof deriveClaimStatus !== "function") {
|
|
34
|
+
process.stderr.write("derive-claim-status: @kontourai/surface deriveClaimStatus missing\n");
|
|
35
|
+
return 2;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let bundle;
|
|
39
|
+
try {
|
|
40
|
+
bundle = JSON.parse(readFileSync(bundlePath, "utf8"));
|
|
41
|
+
} catch (err) {
|
|
42
|
+
process.stderr.write(`derive-claim-status: cannot read bundle: ${err && err.message ? err.message : String(err)}\n`);
|
|
43
|
+
return 3;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const claims = Array.isArray(bundle.claims) ? bundle.claims : [];
|
|
47
|
+
const allEvidence = Array.isArray(bundle.evidence) ? bundle.evidence : [];
|
|
48
|
+
const allEvents = Array.isArray(bundle.events) ? bundle.events : [];
|
|
49
|
+
const allPolicies = Array.isArray(bundle.policies) ? bundle.policies : [];
|
|
50
|
+
|
|
51
|
+
const out = {};
|
|
52
|
+
for (const claim of claims) {
|
|
53
|
+
if (!claim || !claim.id) continue;
|
|
54
|
+
// Filter evidence by claimId (deriveTrustStatus does NOT filter evidence internally — it
|
|
55
|
+
// only filters events — so passing the whole array would let another claim's evidence
|
|
56
|
+
// type/blocking-failure bleed in). Events are filtered internally by claimId; policies are
|
|
57
|
+
// resolved per-claim via verificationPolicyId. This mirrors the producer's per-claim call.
|
|
58
|
+
const evidence = allEvidence.filter((e) => e && e.claimId === claim.id);
|
|
59
|
+
try {
|
|
60
|
+
const { status } = deriveClaimStatus({ claim, evidence, events: allEvents, policies: allPolicies });
|
|
61
|
+
out[claim.id] = status;
|
|
62
|
+
} catch {
|
|
63
|
+
out[claim.id] = null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
process.stdout.write(JSON.stringify(out));
|
|
68
|
+
return 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
main().then((code) => process.exit(code)).catch((err) => {
|
|
72
|
+
process.stderr.write(`derive-claim-status: ${err && err.message ? err.message : String(err)}\n`);
|
|
73
|
+
process.exit(2);
|
|
74
|
+
});
|