@kontourai/flow-agents 3.3.0 → 3.4.1
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/add-to-project.yml +15 -0
- package/.github/workflows/ci.yml +161 -0
- package/CHANGELOG.md +48 -0
- package/CONTEXT.md +5 -1
- package/README.md +19 -8
- package/build/src/builder-flow-run-adapter.d.ts +80 -0
- package/build/src/builder-flow-run-adapter.js +241 -0
- package/build/src/builder-flow-runtime.d.ts +16 -0
- package/build/src/builder-flow-runtime.js +290 -0
- package/build/src/cli/builder-run.d.ts +1 -0
- package/build/src/cli/builder-run.js +27 -0
- package/build/src/cli/effective-backlog-settings.js +70 -2
- package/build/src/cli/init.d.ts +34 -0
- package/build/src/cli/init.js +341 -61
- package/build/src/cli/kit.js +55 -12
- package/build/src/cli/pull-work-provider.js +346 -5
- package/build/src/cli/skill-drift-check.d.ts +1 -0
- package/build/src/cli/skill-drift-check.js +165 -0
- package/build/src/cli/telemetry-doctor.d.ts +37 -0
- package/build/src/cli/telemetry-doctor.js +53 -6
- package/build/src/cli/validate-hook-influence.js +37 -7
- package/build/src/cli/workflow-sidecar.d.ts +93 -8
- package/build/src/cli/workflow-sidecar.js +1175 -158
- package/build/src/cli.js +5 -0
- package/build/src/flow-kit/validate.d.ts +54 -34
- package/build/src/flow-kit/validate.js +237 -26
- package/build/src/index.d.ts +2 -0
- package/build/src/index.js +1 -0
- package/build/src/lib/console-connect-options.d.ts +97 -0
- package/build/src/lib/console-connect-options.js +199 -0
- package/build/src/lib/console-telemetry-validate.d.ts +49 -0
- package/build/src/lib/console-telemetry-validate.js +91 -0
- package/build/src/lib/flow-resolver.d.ts +56 -3
- package/build/src/lib/flow-resolver.js +151 -11
- package/build/src/lib/fs.d.ts +17 -0
- package/build/src/lib/fs.js +172 -0
- package/build/src/lib/local-artifact-root.d.ts +44 -1
- package/build/src/lib/local-artifact-root.js +131 -3
- package/build/src/runtime-adapters.d.ts +39 -3
- package/build/src/runtime-adapters.js +77 -31
- package/build/src/tools/build-universal-bundles.js +40 -2
- package/build/src/tools/codex-agent-routing.d.ts +2 -0
- package/build/src/tools/codex-agent-routing.js +49 -0
- package/build/src/tools/generate-context-map.js +1 -0
- package/build/src/tools/validate-source-tree.js +27 -1
- package/context/scripts/hooks/lib/kit-catalog.js +235 -0
- package/context/scripts/hooks/lib/runnable-command.js +177 -0
- package/context/scripts/hooks/stop-goal-fit.js +278 -48
- package/context/scripts/hooks/workflow-steering.js +121 -21
- package/context/scripts/package.json +3 -0
- package/context/scripts/telemetry/install-console-config.sh +25 -4
- package/context/scripts/telemetry/lib/config.sh +102 -12
- package/context/scripts/telemetry/lib/pricing.sh +50 -0
- package/context/scripts/telemetry/lib/session.sh +3 -0
- package/context/scripts/telemetry/lib/transport.sh +87 -0
- package/context/scripts/telemetry/lib/usage.sh +205 -4
- package/context/scripts/telemetry/telemetry.conf +6 -0
- package/context/scripts/telemetry/telemetry.sh +48 -0
- package/context/settings/workspace-backlog-provider-settings.example.json +48 -0
- package/docs/agent-usage-feedback-loop.md +35 -0
- package/docs/architecture-engine-and-kits.md +110 -0
- package/docs/context-map.md +2 -0
- package/docs/decisions/embeddable-engine.md +152 -0
- package/docs/decisions/index.md +3 -1
- package/docs/decisions/trust-ledger-retention.md +88 -0
- package/docs/decisions/workflow-enforcement.md +31 -9
- package/docs/fixture-ownership.md +3 -0
- package/docs/implementing-trust-reconciliation.md +129 -0
- package/docs/index.md +19 -9
- package/docs/integrations/flow-agents-console.md +167 -0
- package/docs/kit-authoring-guide.md +52 -21
- package/docs/spec/builder-flow-runtime.md +80 -0
- package/docs/spec/runtime-hook-surface.md +45 -1
- package/docs/specs/economics-record-contract.md +270 -0
- package/docs/specs/harness-capability-matrix.md +74 -0
- package/docs/specs/learning-review-proposals-contract.md +340 -0
- package/docs/specs/routing-efficiency-review.md +59 -0
- package/docs/verifiable-trust.md +74 -25
- package/docs/workflow-usage-guide.md +10 -0
- package/evals/acceptance/prove-capture-teeth.sh +132 -0
- package/evals/ci/antigaming-suite.sh +1 -0
- package/evals/ci/run-baseline.sh +72 -4
- package/evals/fixtures/economics/acceptance.json +12 -0
- package/evals/fixtures/economics/agents/tool-worker-1/events.jsonl +2 -0
- package/evals/fixtures/economics/agents/tool-worker-2/events.jsonl +2 -0
- package/evals/fixtures/economics/agents/tool-worker-3/events.jsonl +2 -0
- package/evals/fixtures/economics/agents/tool-worker-4/events.jsonl +1 -0
- package/evals/fixtures/economics/agents/tool-worker-5/events.jsonl +2 -0
- package/evals/fixtures/economics/critique.json +22 -0
- package/evals/fixtures/economics/expected-record.json +71 -0
- package/evals/fixtures/economics/session-usage-event.json +1 -0
- package/evals/fixtures/economics/state.json +11 -0
- package/evals/fixtures/economics/transcript.jsonl +3 -0
- package/evals/fixtures/hook-influence/cases.json +7 -7
- package/evals/fixtures/learning-review-proposals/balanced/economics.jsonl +6 -0
- package/evals/fixtures/learning-review-proposals/effect-follow-up/economics.jsonl +5 -0
- package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-1/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-2/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-3/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-4/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/effect-follow-up/sessions/task-lr-ef-5/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/economics.jsonl +6 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/expected-aggregates.json +30 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/expected-aggregates.md +66 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-1/gate-review.inquiries.json +26 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-1/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-2/gate-review.inquiries.json +26 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-2/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-3/gate-review.inquiries.json +26 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-3/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-4/gate-review.inquiries.json +26 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-4/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-5/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/pattern-present/sessions/task-lr-pp-6/trust.bundle +21 -0
- package/evals/fixtures/learning-review-proposals/repeat-window/economics.jsonl +6 -0
- package/evals/fixtures/learning-review-proposals/under-threshold/economics.jsonl +3 -0
- package/evals/fixtures/telemetry/usage-transcript-sample.jsonl +4 -0
- package/evals/fixtures/trust-reconcile-exploits/mcp-degrade.json +42 -0
- package/evals/integration/test_builder_entry_enforcement.sh +241 -0
- package/evals/integration/test_builder_step_producers.sh +18 -10
- package/evals/integration/test_bundle_install.sh +172 -0
- package/evals/integration/test_console_tenant_isolation.sh +167 -0
- package/evals/integration/test_critique_supersession_roundtrip.sh +4 -1
- package/evals/integration/test_dual_emit_flow_step.sh +10 -4
- package/evals/integration/test_economics_record.sh +674 -0
- package/evals/integration/test_effective_backlog_settings.sh +1 -1
- package/evals/integration/test_evidence_capture_hook.sh +17 -2
- package/evals/integration/test_exemption_usage_review.sh +198 -0
- package/evals/integration/test_fixture_retirement_audit.sh +2 -2
- package/evals/integration/test_flow_kit_install_git.sh +83 -0
- package/evals/integration/test_flowdef_session_activation.sh +0 -1
- package/evals/integration/test_flowdef_session_history_preservation.sh +13 -3
- package/evals/integration/test_gate_lockdown.sh +7 -0
- package/evals/integration/test_gate_review_inquiry_records.sh +9 -1
- package/evals/integration/test_goal_fit_hook.sh +2031 -0
- package/evals/integration/test_hook_category_behaviors.sh +8 -1
- package/evals/integration/test_hook_influence_cases.sh +25 -1
- package/evals/integration/test_install_merge.sh +227 -2
- package/evals/integration/test_kit_conformance_levels.sh +6 -6
- package/evals/integration/test_learning_review_proposals.sh +329 -0
- package/evals/integration/test_liveness_conflict_injection.sh +26 -22
- package/evals/integration/test_liveness_console_relay.sh +166 -0
- package/evals/integration/test_liveness_heartbeat.sh +17 -17
- package/evals/integration/test_liveness_worktree_root.sh +575 -0
- package/evals/integration/test_phase_map_and_gate_claim.sh +6 -1
- package/evals/integration/test_publish_delivery.sh +331 -1
- package/evals/integration/test_pull_work_board.sh +200 -0
- package/evals/integration/test_pull_work_provider.sh +1 -1
- package/evals/integration/test_record_check.sh +378 -0
- package/evals/integration/test_routing_efficiency.sh +71 -0
- package/evals/integration/test_runtime_adapter_activation.sh +28 -0
- package/evals/integration/test_session_resume_roundtrip.sh +16 -19
- package/evals/integration/test_skill_drift_check.sh +870 -0
- package/evals/integration/test_telemetry.sh +445 -0
- package/evals/integration/test_telemetry_doctor.sh +66 -0
- package/evals/integration/test_telemetry_usage_pipeline.sh +228 -0
- package/evals/integration/test_trust_reconcile_negatives.sh +30 -13
- package/evals/integration/test_trust_reconcile_trailer_diagnostic.sh +247 -0
- package/evals/integration/test_usage_cost.sh +61 -0
- package/evals/integration/test_workflow_sidecar_writer.sh +1395 -0
- package/evals/integration/test_workflow_steering_hook.sh +157 -16
- package/evals/integration/test_workspace_settings.sh +176 -0
- package/evals/lib/env.sh +26 -0
- package/evals/lib/node.sh +8 -0
- package/evals/run.sh +29 -0
- package/evals/static/test_ci_integration_coverage.sh +115 -0
- package/evals/static/test_declared_scope_forms_documented.sh +114 -0
- package/evals/static/test_universal_bundles.sh +34 -0
- package/evals/static/test_validate_source_kit_asset_scope.sh +259 -0
- package/evals/static/test_workflow_skills.sh +1 -1
- package/kits/builder/flows/build.flow.json +9 -18
- package/kits/builder/flows/publish-learn.flow.json +5 -1
- package/kits/builder/kit.json +120 -0
- package/kits/builder/skills/deliver/SKILL.md +42 -0
- package/kits/builder/skills/evidence-gate/SKILL.md +12 -0
- package/kits/builder/skills/execute-plan/SKILL.md +9 -0
- package/kits/builder/skills/learning-review/SKILL.md +51 -0
- package/kits/builder/skills/plan-work/SKILL.md +17 -20
- package/kits/builder/skills/pull-work/SKILL.md +21 -0
- package/kits/builder/skills/release-readiness/SKILL.md +12 -0
- package/kits/knowledge/kit.json +9 -0
- package/kits/veritas-governance/docs/README.md +35 -7
- package/kits/veritas-governance/fixtures/exemption-review/mixed-fresh-stale.DECLARED.json +14 -0
- package/kits/veritas-governance/kit.json +14 -0
- package/kits/veritas-governance/skills/exemption-usage-review/SKILL.md +128 -0
- package/kits/veritas-governance/skills/exemption-usage-review/review-exemptions.mjs +231 -0
- package/package.json +2 -2
- package/packaging/manifest.json +29 -0
- package/schemas/backlog-provider-settings.schema.json +13 -0
- package/schemas/workflow-state.schema.json +44 -0
- package/scripts/README.md +4 -0
- package/scripts/check-content-boundary.cjs +8 -1
- package/scripts/ci/trust-reconcile.js +136 -0
- package/scripts/hooks/codex-hook-adapter.js +77 -2
- package/scripts/hooks/evidence-capture.js +38 -5
- package/scripts/hooks/lib/codex-exit-code.js +316 -0
- package/scripts/hooks/lib/kit-catalog.js +235 -0
- package/scripts/hooks/lib/liveness-write.js +28 -1
- package/scripts/hooks/lib/local-artifact-paths.js +97 -1
- package/scripts/hooks/lib/runnable-command.js +177 -0
- package/scripts/hooks/lib/skill-drift.js +350 -0
- package/scripts/hooks/stop-goal-fit.js +278 -48
- package/scripts/hooks/workflow-steering.js +121 -21
- package/scripts/install-codex-home.sh +97 -47
- package/scripts/install-merge.js +72 -14
- package/scripts/install-owned-files.js +178 -0
- package/scripts/liveness/relay.sh +84 -0
- package/scripts/telemetry/economics-record.schema.json +145 -0
- package/scripts/telemetry/economics-record.sh +331 -0
- package/scripts/telemetry/install-console-config.sh +25 -4
- package/scripts/telemetry/learning-review-decide.sh +124 -0
- package/scripts/telemetry/learning-review-proposals.schema.json +161 -0
- package/scripts/telemetry/learning-review-proposals.sh +484 -0
- package/scripts/telemetry/lib/config.sh +102 -12
- package/scripts/telemetry/lib/pricing.sh +14 -6
- package/scripts/telemetry/lib/session.sh +3 -0
- package/scripts/telemetry/lib/transport.sh +133 -15
- package/scripts/telemetry/lib/usage.sh +121 -28
- package/scripts/telemetry/routing-efficiency.sh +0 -0
- package/scripts/telemetry/telemetry.conf +6 -0
- package/scripts/telemetry/telemetry.sh +48 -0
- package/src/builder-flow-run-adapter.ts +357 -0
- package/src/builder-flow-runtime.ts +348 -0
- package/src/cli/builder-flow-run-adapter.test.mjs +495 -0
- package/src/cli/builder-flow-runtime.test.mjs +213 -0
- package/src/cli/builder-run.ts +28 -0
- package/src/cli/codex-agent-routing.test.mjs +44 -0
- package/src/cli/codex-exit-code.test.mjs +207 -0
- package/src/cli/console-connect-options.test.mjs +329 -0
- package/src/cli/console-telemetry-validate.test.mjs +157 -0
- package/src/cli/effective-backlog-settings.ts +68 -2
- package/src/cli/flow-resolver-composition.test.mjs +101 -0
- package/src/cli/init.test.mjs +161 -0
- package/src/cli/init.ts +407 -62
- package/src/cli/kit-metadata-security.test.mjs +443 -0
- package/src/cli/kit.ts +50 -12
- package/src/cli/pull-work-provider.ts +377 -3
- package/src/cli/sidecar-pure-helpers.test.mjs +64 -0
- package/src/cli/skill-drift-check.ts +196 -0
- package/src/cli/telemetry-doctor.test.mjs +53 -0
- package/src/cli/telemetry-doctor.ts +50 -7
- package/src/cli/validate-hook-influence.ts +37 -6
- package/src/cli/workflow-sidecar.ts +1150 -151
- package/src/cli.ts +5 -0
- package/src/flow-kit/validate.ts +277 -38
- package/src/index.ts +19 -0
- package/src/lib/console-connect-options.ts +261 -0
- package/src/lib/console-telemetry-validate.ts +88 -0
- package/src/lib/flow-resolver.ts +153 -10
- package/src/lib/fs.ts +160 -0
- package/src/lib/local-artifact-root.ts +129 -3
- package/src/runtime-adapters.ts +113 -33
- package/src/tools/build-universal-bundles.ts +36 -2
- package/src/tools/codex-agent-routing.ts +48 -0
- package/src/tools/generate-context-map.ts +1 -0
- package/src/tools/validate-source-tree.ts +26 -1
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { parseArgs, flagBool, flagString } from "../lib/args.js";
|
|
6
|
+
import { skillsManifestPath } from "../lib/local-artifact-root.js";
|
|
7
|
+
import { root } from "../tools/common.js";
|
|
8
|
+
import { globalDest, ensureBundle } from "./init.js";
|
|
9
|
+
const REFRESH_COMMAND_LINE = "Run `flow-agents init --runtime claude-code --global` to refresh drifted or missing skill copies " +
|
|
10
|
+
"(user-modified files are reported here, not overwritten — review them before refreshing).";
|
|
11
|
+
function usage() {
|
|
12
|
+
console.log(`usage: flow-agents skill-drift-check [options]
|
|
13
|
+
|
|
14
|
+
Read-only comparison of installed Claude Code skill files against the current
|
|
15
|
+
kit/bundle source and the last-recorded install manifest. Never writes under
|
|
16
|
+
the installed skills directory or the kit source directory.
|
|
17
|
+
|
|
18
|
+
Options:
|
|
19
|
+
--dest PATH Installed Claude Code global destination (contains
|
|
20
|
+
skills/ and .flow-agents/skills-manifest.json).
|
|
21
|
+
Defaults to the global claude-code destination
|
|
22
|
+
(honors FLOW_AGENTS_USER_CLAUDE_SETTINGS).
|
|
23
|
+
--kit-source-dir PATH Kit/bundle skills source directory to compare
|
|
24
|
+
against. Defaults to the current built bundle's
|
|
25
|
+
.claude/skills directory.
|
|
26
|
+
--json Emit the machine-readable drift report as JSON.
|
|
27
|
+
--help, -h Show this help.
|
|
28
|
+
|
|
29
|
+
Exit codes:
|
|
30
|
+
0 Clean — every checked file is in_sync, no unbaselined/missing entries.
|
|
31
|
+
1 Drift found — at least one file is not in_sync (kit_updated,
|
|
32
|
+
user_modified, unbaselined, missing_install, or kit_removed).
|
|
33
|
+
2 Cannot fully check — no manifest exists yet, or <dest>/skills is
|
|
34
|
+
missing. Reported with guidance, not a stack trace.
|
|
35
|
+
`);
|
|
36
|
+
}
|
|
37
|
+
function loadSkillDriftLib() {
|
|
38
|
+
const skillDriftLibPath = path.join(root, "scripts", "hooks", "lib", "skill-drift.js");
|
|
39
|
+
const _require = createRequire(import.meta.url);
|
|
40
|
+
return _require(skillDriftLibPath);
|
|
41
|
+
}
|
|
42
|
+
function printStatePaths(report, state, label) {
|
|
43
|
+
const paths = report.files.filter((entry) => entry.state === state).map((entry) => entry.path);
|
|
44
|
+
if (!paths.length)
|
|
45
|
+
return;
|
|
46
|
+
console.log(` ${label}:`);
|
|
47
|
+
for (const p of paths)
|
|
48
|
+
console.log(` - ${p}`);
|
|
49
|
+
}
|
|
50
|
+
function printReport(report) {
|
|
51
|
+
const s = report.summary;
|
|
52
|
+
console.log(`Skill drift check: ${report.installedDir}`);
|
|
53
|
+
console.log(` kit source: ${report.kitSourceDir}`);
|
|
54
|
+
console.log(` manifest found: ${report.manifestFound ? "yes" : "no"}`);
|
|
55
|
+
console.log(` total ${s.total} — in_sync ${s.inSync}, kit_updated ${s.kitUpdated}, user_modified ${s.userModified}, ` +
|
|
56
|
+
`unbaselined ${s.unbaselined}, missing_install ${s.missingInstall}, kit_removed ${s.kitRemoved}`);
|
|
57
|
+
printStatePaths(report, "kit_updated", "kit_updated (installed matches last recorded baseline; kit source has changed)");
|
|
58
|
+
printStatePaths(report, "user_modified", "user_modified (installed bytes changed locally since last recorded install)");
|
|
59
|
+
printStatePaths(report, "unbaselined", "unbaselined (no recorded baseline for this file yet)");
|
|
60
|
+
printStatePaths(report, "missing_install", "missing_install (present in kit source, not installed yet)");
|
|
61
|
+
printStatePaths(report, "kit_removed", "kit_removed (installed file no longer present in current kit source)");
|
|
62
|
+
if (report.hasDrift) {
|
|
63
|
+
console.log("");
|
|
64
|
+
console.log(REFRESH_COMMAND_LINE);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
console.log("");
|
|
68
|
+
console.log("All installed skill files are in sync with the current kit source.");
|
|
69
|
+
}
|
|
70
|
+
if (s.kitRemoved > 0) {
|
|
71
|
+
console.log("");
|
|
72
|
+
console.log("kit_removed files were removed from the current kit source; refresh will NOT delete them " +
|
|
73
|
+
"— review and remove them manually if appropriate.");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
export async function main(argv = process.argv.slice(2)) {
|
|
77
|
+
// Whole-body error containment (#439 review fix): any unexpected error (a bug in this CLI, a
|
|
78
|
+
// filesystem error not already handled below, etc.) must never surface as a raw stack trace to
|
|
79
|
+
// the user — print one stderr line and exit 2 ("cannot fully check"), same exit code already
|
|
80
|
+
// used for the other known cannot-check conditions below.
|
|
81
|
+
try {
|
|
82
|
+
const args = parseArgs(argv);
|
|
83
|
+
if (flagBool(args.flags, "help") || flagBool(args.flags, "h")) {
|
|
84
|
+
usage();
|
|
85
|
+
return 0;
|
|
86
|
+
}
|
|
87
|
+
const dest = path.resolve(flagString(args.flags, "dest") ?? globalDest("claude-code"));
|
|
88
|
+
const installedDir = path.join(dest, "skills");
|
|
89
|
+
let kitSourceDir;
|
|
90
|
+
const kitSourceOverride = flagString(args.flags, "kit-source-dir");
|
|
91
|
+
if (kitSourceOverride) {
|
|
92
|
+
kitSourceDir = path.resolve(kitSourceOverride);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
try {
|
|
96
|
+
kitSourceDir = path.join(ensureBundle("claude-code"), ".claude", "skills");
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
console.error(`flow-agents skill-drift-check: could not resolve current kit source: ${error.message}`);
|
|
100
|
+
console.error("Pass --kit-source-dir explicitly, or build the bundle first.");
|
|
101
|
+
return 2;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const { loadManifest, compareSkillDrift } = loadSkillDriftLib();
|
|
105
|
+
const manifestPath = skillsManifestPath(dest);
|
|
106
|
+
const manifest = loadManifest(manifestPath);
|
|
107
|
+
const installedDirExists = fs.existsSync(installedDir);
|
|
108
|
+
// Cannot fully check when there is no recorded baseline manifest at all yet, OR the installed
|
|
109
|
+
// skills directory itself does not exist — reported explicitly with guidance, never silently
|
|
110
|
+
// mis-reported as in_sync and never a stack trace (plan's exit-code-2 contract; see also the
|
|
111
|
+
// "drifted-but-never-manifested install" stop-short risk).
|
|
112
|
+
if (!manifest || !installedDirExists) {
|
|
113
|
+
const reasons = [];
|
|
114
|
+
if (!manifest)
|
|
115
|
+
reasons.push(`no manifest found at ${manifestPath}`);
|
|
116
|
+
if (!installedDirExists)
|
|
117
|
+
reasons.push(`installed skills directory does not exist at ${installedDir}`);
|
|
118
|
+
if (flagBool(args.flags, "json")) {
|
|
119
|
+
console.log(JSON.stringify({ ok: false, reason: reasons.join("; "), manifestFound: Boolean(manifest), installedDirExists, manifestPath, installedDir, kitSourceDir }, null, 2));
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
console.error(`flow-agents skill-drift-check: cannot fully check — ${reasons.join("; ")}.`);
|
|
123
|
+
console.error("Run this once to establish or refresh a baseline, then re-run skill-drift-check:");
|
|
124
|
+
console.error(REFRESH_COMMAND_LINE);
|
|
125
|
+
}
|
|
126
|
+
return 2;
|
|
127
|
+
}
|
|
128
|
+
const report = compareSkillDrift({ installedDir, kitSourceDir, manifest });
|
|
129
|
+
if (flagBool(args.flags, "json")) {
|
|
130
|
+
console.log(JSON.stringify(report, null, 2));
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
printReport(report);
|
|
134
|
+
}
|
|
135
|
+
// Exit 1 iff any file is not in_sync — kit_updated, user_modified, unbaselined,
|
|
136
|
+
// missing_install, and kit_removed are ALL drift (plan's five/six-state exit-code contract;
|
|
137
|
+
// previously only kit_updated/user_modified tripped this, silently exiting 0 for
|
|
138
|
+
// unbaselined/missing_install/kit_removed despite printing drift guidance).
|
|
139
|
+
if (report.hasDrift)
|
|
140
|
+
return 1;
|
|
141
|
+
return 0;
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
console.error(`flow-agents skill-drift-check: unexpected error: ${error.message}`);
|
|
145
|
+
return 2;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// Use process.exitCode (not process.exit) to allow stdout to be flushed before exit.
|
|
149
|
+
// Resolve real paths to handle symlinks (e.g. /tmp -> /private/tmp on macOS) so the
|
|
150
|
+
// entry-point guard fires correctly when the module is loaded directly as a script.
|
|
151
|
+
const _selfRealPath = (() => { try {
|
|
152
|
+
return fs.realpathSync(fileURLToPath(import.meta.url));
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return fileURLToPath(import.meta.url);
|
|
156
|
+
} })();
|
|
157
|
+
const _argv1RealPath = (() => { try {
|
|
158
|
+
return fs.realpathSync(process.argv[1]);
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return process.argv[1];
|
|
162
|
+
} })();
|
|
163
|
+
if (_selfRealPath === _argv1RealPath) {
|
|
164
|
+
main().then((code) => { process.exitCode = code; }).catch((err) => { console.error(err); process.exitCode = 1; });
|
|
165
|
+
}
|
|
@@ -1 +1,38 @@
|
|
|
1
|
+
type Reachability = {
|
|
2
|
+
checked: boolean;
|
|
3
|
+
ok: boolean | null;
|
|
4
|
+
statusCode?: number;
|
|
5
|
+
error?: string;
|
|
6
|
+
};
|
|
7
|
+
export type DoctorReport = {
|
|
8
|
+
ok: boolean;
|
|
9
|
+
destination: string;
|
|
10
|
+
telemetry: {
|
|
11
|
+
configFile: string;
|
|
12
|
+
configExists: boolean;
|
|
13
|
+
enabled: boolean;
|
|
14
|
+
dataDir: string;
|
|
15
|
+
sessionDir: string;
|
|
16
|
+
channels: Array<{
|
|
17
|
+
name: string;
|
|
18
|
+
logFile?: string;
|
|
19
|
+
endpointUrl?: string;
|
|
20
|
+
active: boolean;
|
|
21
|
+
}>;
|
|
22
|
+
activeSinks: string[];
|
|
23
|
+
};
|
|
24
|
+
console: {
|
|
25
|
+
sink: "local-only" | "console";
|
|
26
|
+
url?: string;
|
|
27
|
+
endpointUrl?: string;
|
|
28
|
+
endpointAllowed: boolean;
|
|
29
|
+
tokenConfigured: boolean;
|
|
30
|
+
tenantConfigured: boolean;
|
|
31
|
+
reachability: Reachability;
|
|
32
|
+
};
|
|
33
|
+
warnings: string[];
|
|
34
|
+
};
|
|
35
|
+
export declare function endpointAllowed(endpointUrl: string, allowNetwork?: boolean): boolean;
|
|
36
|
+
export declare function buildReport(argv: string[]): Promise<DoctorReport>;
|
|
1
37
|
export declare function main(argv?: string[]): Promise<number>;
|
|
38
|
+
export {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as http from "node:http";
|
|
3
3
|
import * as https from "node:https";
|
|
4
|
+
import * as os from "node:os";
|
|
4
5
|
import * as path from "node:path";
|
|
5
6
|
import { parseArgs, flagBool, flagString } from "../lib/args.js";
|
|
6
7
|
import { telemetryDataDir as defaultTelemetryDataDir } from "../lib/local-artifact-root.js";
|
|
@@ -61,7 +62,18 @@ function deriveConsoleEndpoint(consoleUrl, explicitEndpoint) {
|
|
|
61
62
|
return `${base}/records`;
|
|
62
63
|
return `${base}/api/telemetry/records`;
|
|
63
64
|
}
|
|
64
|
-
|
|
65
|
+
// The one known hosted Kontour Console host is reachability-checkable by
|
|
66
|
+
// default (no --allow-network): reuses the exact override knob
|
|
67
|
+
// scripts/telemetry/console-presets.sh already defines
|
|
68
|
+
// (FLOW_AGENTS_KONTOUR_CLOUD_CONSOLE_URL / https://console.kontourai.io), so
|
|
69
|
+
// it self-corrects if that default ever changes rather than drifting via a
|
|
70
|
+
// second hardcoded literal. Scoped to this one hostname only -- a generic
|
|
71
|
+
// non-local HTTPS endpoint still requires --allow-network exactly as today.
|
|
72
|
+
function isKnownHostedConsoleHostname(hostname) {
|
|
73
|
+
const hostedUrl = parseUrl(process.env["FLOW_AGENTS_KONTOUR_CLOUD_CONSOLE_URL"] ?? "https://console.kontourai.io");
|
|
74
|
+
return hostedUrl !== null && hostname === hostedUrl.hostname;
|
|
75
|
+
}
|
|
76
|
+
export function endpointAllowed(endpointUrl, allowNetwork = false) {
|
|
65
77
|
if (!endpointUrl || endpointUrl.includes("\n") || endpointUrl.includes("\r") || endpointUrl.includes('"'))
|
|
66
78
|
return false;
|
|
67
79
|
const url = parseUrl(endpointUrl);
|
|
@@ -70,7 +82,7 @@ function endpointAllowed(endpointUrl, allowNetwork = false) {
|
|
|
70
82
|
if (url.username || url.password)
|
|
71
83
|
return false;
|
|
72
84
|
if (url.protocol === "https:")
|
|
73
|
-
return allowNetwork || isLocalHostname(url.hostname);
|
|
85
|
+
return allowNetwork || isLocalHostname(url.hostname) || isKnownHostedConsoleHostname(url.hostname);
|
|
74
86
|
return url.protocol === "http:" && isLocalHostname(url.hostname);
|
|
75
87
|
}
|
|
76
88
|
function activeSinks(enabled, channels, consoleEndpoint, allowed) {
|
|
@@ -126,6 +138,31 @@ function parseUrl(value) {
|
|
|
126
138
|
function isLocalHostname(hostname) {
|
|
127
139
|
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname.endsWith(".localhost");
|
|
128
140
|
}
|
|
141
|
+
// TS mirror of scripts/telemetry/lib/config.sh's telemetry_conf_trusted
|
|
142
|
+
// (mode-600 + owner-uid + no-symlink gate). Used only to WARN that a
|
|
143
|
+
// candidate telemetry-console.conf exists but would be silently ignored by
|
|
144
|
+
// the real bash runtime pipeline -- this doctor still reads the shipped
|
|
145
|
+
// scripts/telemetry/telemetry.conf default for its own report (accepted,
|
|
146
|
+
// explicitly out-of-scope gap; see install-flow-foundations plan Thread C).
|
|
147
|
+
// Symlink-then-existence guard order mirrors console-learning-projection.ts
|
|
148
|
+
// and workflow-sidecar.ts's existing lstatSync-before-statSync precedent.
|
|
149
|
+
function isConfTrusted(file) {
|
|
150
|
+
try {
|
|
151
|
+
if (fs.lstatSync(file).isSymbolicLink())
|
|
152
|
+
return false;
|
|
153
|
+
const stat = fs.statSync(file);
|
|
154
|
+
if (!stat.isFile())
|
|
155
|
+
return false;
|
|
156
|
+
if ((stat.mode & 0o777) !== 0o600)
|
|
157
|
+
return false;
|
|
158
|
+
if (typeof process.getuid === "function" && stat.uid !== process.getuid())
|
|
159
|
+
return false;
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
129
166
|
function safeReportUrl(value) {
|
|
130
167
|
if (!value)
|
|
131
168
|
return undefined;
|
|
@@ -140,7 +177,7 @@ function safeReportUrl(value) {
|
|
|
140
177
|
}
|
|
141
178
|
return parsed.toString();
|
|
142
179
|
}
|
|
143
|
-
async function buildReport(argv) {
|
|
180
|
+
export async function buildReport(argv) {
|
|
144
181
|
const args = parseArgs(argv);
|
|
145
182
|
const allowNetwork = flagBool(args.flags, "allow-network");
|
|
146
183
|
const dest = path.resolve(flagString(args.flags, "dest", process.cwd()) ?? process.cwd());
|
|
@@ -163,7 +200,7 @@ async function buildReport(argv) {
|
|
|
163
200
|
const allowed = endpointAllowed(endpointUrl, allowNetwork);
|
|
164
201
|
const timeoutMs = Number.parseInt(flagString(args.flags, "timeout-ms", "2000") ?? "2000", 10);
|
|
165
202
|
const reachability = await checkConsoleReachability(endpointUrl, Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : 2000, allowNetwork);
|
|
166
|
-
const warnings = reportWarnings(configFile, endpointUrl, allowed, allowNetwork);
|
|
203
|
+
const warnings = reportWarnings(configFile, endpointUrl, allowed, allowNetwork, dest);
|
|
167
204
|
return {
|
|
168
205
|
ok: enabled && (!endpointUrl || (allowed && reachability.ok !== false)),
|
|
169
206
|
destination: dest,
|
|
@@ -188,12 +225,22 @@ async function buildReport(argv) {
|
|
|
188
225
|
warnings,
|
|
189
226
|
};
|
|
190
227
|
}
|
|
191
|
-
function reportWarnings(configFile, endpointUrl, allowed, allowNetwork) {
|
|
228
|
+
function reportWarnings(configFile, endpointUrl, allowed, allowNetwork, dest) {
|
|
192
229
|
const warnings = [];
|
|
193
230
|
if (!fs.existsSync(configFile))
|
|
194
231
|
warnings.push("telemetry.conf was not found under destination scripts/telemetry");
|
|
195
232
|
if (endpointUrl && !allowed)
|
|
196
|
-
warnings.push(allowNetwork ? "Console endpoint is malformed or contains credentials" : "Console endpoint is not allowed without --allow-network; local http(s) endpoints are allowed by default");
|
|
233
|
+
warnings.push(allowNetwork ? "Console endpoint is malformed or contains credentials" : "Console endpoint is not allowed without --allow-network; local http(s) endpoints and the known hosted Console host are allowed by default");
|
|
234
|
+
// Mirrors config.sh's local-before-global precedence for the warning only
|
|
235
|
+
// (see isConfTrusted's doc comment for the accepted config-resolution-parity gap).
|
|
236
|
+
const localConf = path.join(dest, ".kontourai", "telemetry-console.conf");
|
|
237
|
+
const globalConf = path.join(os.homedir(), ".flow-agents", "telemetry-console.conf");
|
|
238
|
+
if (fs.existsSync(localConf) && !isConfTrusted(localConf)) {
|
|
239
|
+
warnings.push(`Untrusted telemetry console conf at ${localConf} (must be mode 600, owned by the current user, and not a symlink); it is being ignored and telemetry stays fail-open`);
|
|
240
|
+
}
|
|
241
|
+
else if (fs.existsSync(globalConf) && !isConfTrusted(globalConf)) {
|
|
242
|
+
warnings.push(`Untrusted telemetry console conf at ${globalConf} (must be mode 600, owned by the current user, and not a symlink); it is being ignored and telemetry stays fail-open`);
|
|
243
|
+
}
|
|
197
244
|
return warnings;
|
|
198
245
|
}
|
|
199
246
|
function printText(report) {
|
|
@@ -1,18 +1,47 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { readKitInventory } from "../runtime-adapters.js";
|
|
4
|
+
import { root } from "../tools/common.js";
|
|
3
5
|
const validTiers = new Set(["adapter", "design-target", "installed-command", "live-acceptance", "documented-runtime-gap"]);
|
|
4
6
|
const validRuntimes = new Set(["codex", "claude-code", "kiro-cli"]);
|
|
5
7
|
const validHooks = new Set(["workflow-steering", "stop-goal-fit"]);
|
|
6
8
|
const validEvents = new Set(["UserPromptSubmit", "PostToolUse", "Stop"]);
|
|
7
|
-
const
|
|
8
|
-
"dev-builder-build-requires-pickup-probe-before-plan": { description: "missing pickup Probe before plan", tier: "design-target", must_include: ["design-probe", "accepted gaps", "provider_state", "conflict_risks"], agent_must_do: ["route decision_gap back to design-probe/pickup Probe"] },
|
|
9
|
-
"dev-builder-review-before-verify-after-execute": { description: "review-before-verify after execute", tier: "adapter", event: "PostToolUse", must_include: ["Next: review", "then verify", "report only"], agent_must_do: ["review-work for report-only critique before verify-work", "not count critique.json as verification evidence"] },
|
|
10
|
-
"dev-builder-route-fresh-coding-prompt": { description: "fresh coding prompt routes into Builder workflow", tier: "adapter", event: "UserPromptSubmit", must_include: ["BUILDER WORKFLOW ROUTE", "activate `deliver`", "--flow-id builder.build", "plan-work -> execute-plan -> review-work -> verify-work", "publish/release-readiness and learning-review"], agent_must_do: ["activate Builder Kit delivery workflow before source edits", "use deliver by default for coding/build work", "do not bypass plan-work -> execute-plan -> review-work -> verify-work", "publish, release readiness, and learning feedback"] },
|
|
9
|
+
const engineRequiredCases = {
|
|
11
10
|
"dev-verify-fail-preserves-trace-before-rework": { description: "verify failure route-back with preserved FAIL evidence", tier: "design-target", must_include: ["If verdict=FAIL: record the FAIL artifact"], agent_must_do: ["write the FAIL artifact before routing back", "route to plan"] },
|
|
12
11
|
"codex-claude-strict-stop-adapter-contract": { description: "Goal Fit stop adapter behavior", tier: "adapter", hook: "stop-goal-fit", event: "Stop", must_include: ["Goal Fit warning"], agent_must_do: ["treat strict Stop guidance as a blocker"] },
|
|
13
12
|
"codex-live-context-gap": { description: "Codex documented runtime gap", tier: "documented-runtime-gap", must_include: ["STATE:", "not_verified"], agent_must_do: ["record NOT_VERIFIED for live influence"] },
|
|
14
13
|
"installed-command-protocol-guidance": { description: "installed-command evidence lane", tier: "installed-command", must_include: ["STATE:", "CRITIQUE:", "CONTEXT MAP:"], agent_must_do: ["treat hook guidance as additional context"] },
|
|
15
14
|
};
|
|
15
|
+
function requiredCases() {
|
|
16
|
+
const dest = process.env.FLOW_AGENTS_HOOK_INFLUENCE_DEST ?? root;
|
|
17
|
+
const sourceRoot = process.env.FLOW_AGENTS_HOOK_INFLUENCE_SOURCE_ROOT ?? root;
|
|
18
|
+
const inventory = readKitInventory(sourceRoot, dest);
|
|
19
|
+
if (inventory.errors.length) {
|
|
20
|
+
fail(`kit hook influence metadata is invalid:\n${inventory.errors.join("\n")}`);
|
|
21
|
+
}
|
|
22
|
+
const kitCases = {};
|
|
23
|
+
for (const expectation of inventory.hook_influence_expectations) {
|
|
24
|
+
const namespacedId = `kit:${expectation.kit_id}:${expectation.id}`;
|
|
25
|
+
if (engineRequiredCases[expectation.id]) {
|
|
26
|
+
fail(`${expectation.kit_id}: hook influence expectation id collides with engine-required case id: ${expectation.id}`);
|
|
27
|
+
}
|
|
28
|
+
if (engineRequiredCases[namespacedId]) {
|
|
29
|
+
fail(`${expectation.kit_id}: namespaced hook influence expectation id collides with engine-required case id: ${namespacedId}`);
|
|
30
|
+
}
|
|
31
|
+
if (kitCases[namespacedId]) {
|
|
32
|
+
fail(`${expectation.kit_id}: duplicate namespaced hook influence expectation id: ${namespacedId}`);
|
|
33
|
+
}
|
|
34
|
+
kitCases[namespacedId] = {
|
|
35
|
+
description: expectation.description,
|
|
36
|
+
tier: expectation.tier,
|
|
37
|
+
...(expectation.hook ? { hook: expectation.hook } : {}),
|
|
38
|
+
...(expectation.event ? { event: expectation.event } : {}),
|
|
39
|
+
must_include: expectation.must_include_guidance,
|
|
40
|
+
agent_must_do: expectation.must_include_actions,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return { ...engineRequiredCases, ...kitCases };
|
|
44
|
+
}
|
|
16
45
|
function fail(message) {
|
|
17
46
|
throw new Error(message);
|
|
18
47
|
}
|
|
@@ -65,6 +94,7 @@ function validateEvidenceCommand(caseId, runtimes, evidence) {
|
|
|
65
94
|
export function main(argv = process.argv.slice(2)) {
|
|
66
95
|
try {
|
|
67
96
|
const file = argv[0] ?? "evals/fixtures/hook-influence/cases.json";
|
|
97
|
+
const required = requiredCases();
|
|
68
98
|
const data = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
69
99
|
if (data.schema_version !== "1.0")
|
|
70
100
|
fail("schema_version must be 1.0");
|
|
@@ -108,7 +138,7 @@ export function main(argv = process.argv.slice(2)) {
|
|
|
108
138
|
["command", "status"].forEach((key) => { if (typeof evidence[key] !== "string" || !String(evidence[key]).trim())
|
|
109
139
|
fail(`${caseId}: evidence.${key} must be a non-empty string`); });
|
|
110
140
|
validateEvidenceCommand(caseId, runtimes, evidence);
|
|
111
|
-
const req =
|
|
141
|
+
const req = required[caseId];
|
|
112
142
|
if (req) {
|
|
113
143
|
if (tier !== req.tier)
|
|
114
144
|
fail(`${caseId}: ${req.description} must use ${req.tier} evidence tier`);
|
|
@@ -132,9 +162,9 @@ export function main(argv = process.argv.slice(2)) {
|
|
|
132
162
|
for (const tier of ["adapter", "installed-command", "live-acceptance", "documented-runtime-gap"])
|
|
133
163
|
if (!byTier.has(tier))
|
|
134
164
|
fail(`missing required #62 evidence tier(s): ${tier}`);
|
|
135
|
-
for (const id of Object.keys(
|
|
165
|
+
for (const id of Object.keys(required))
|
|
136
166
|
if (!casesById.has(id))
|
|
137
|
-
fail(`missing required #62 case for ${
|
|
167
|
+
fail(`missing required #62 case for ${required[id].description}: ${id}`);
|
|
138
168
|
const codexGap = casesById.get("codex-live-context-gap");
|
|
139
169
|
const codexRuntimes = codexGap.runtime_scope;
|
|
140
170
|
const codexEvidence = codexGap.evidence;
|
|
@@ -133,6 +133,36 @@ type SurfaceModule = {
|
|
|
133
133
|
assuranceLevel: "signed";
|
|
134
134
|
} | null>;
|
|
135
135
|
};
|
|
136
|
+
/**
|
|
137
|
+
* Fold a raw command-log (command-log.jsonl entries) into a per-command classification,
|
|
138
|
+
* keyed by normalized command text (whitespace-collapsed, trimmed). Pure and side-effect-free
|
|
139
|
+
* so it is directly unit-testable (#470 iteration 2, finding #2 — HIGH: the prior inline reducer
|
|
140
|
+
* collapsed every non-"fail" `observedResult` — including "ambiguous" — to "pass", feeding a
|
|
141
|
+
* verified event + `passing:true` and reintroducing the #470 false-pass through the trust-bundle
|
|
142
|
+
* reconciliation path).
|
|
143
|
+
*
|
|
144
|
+
* Three-way classification, keyed on `observedResult` (never re-derived from `exitCode` alone,
|
|
145
|
+
* which would miscoerce the #362 grep/diff absence carve-out `ambiguous,exitCode:1` entry to
|
|
146
|
+
* `fail`):
|
|
147
|
+
* - "fail" when `observedResult==="fail"`, or (legacy, no observedResult) a nonzero
|
|
148
|
+
* integer `exitCode`.
|
|
149
|
+
* - "ambiguous" when `observedResult==="ambiguous"`, or (legacy) `exitCode` is `null` with no
|
|
150
|
+
* fail signal.
|
|
151
|
+
* - "pass" when `observedResult==="pass"`, or (legacy) `exitCode===0`.
|
|
152
|
+
*
|
|
153
|
+
* Precedence across repeated entries for the same command: fail > pass > ambiguous. A genuine
|
|
154
|
+
* exit-0 pass is positive evidence and confirms; ambiguous holds only when there is neither a
|
|
155
|
+
* fail nor a positive pass, since a "pass" always requires positive evidence.
|
|
156
|
+
*
|
|
157
|
+
* The caller (buildTrustBundle) maps "ambiguous" onto the existing canonical non-confirming
|
|
158
|
+
* status `not_verified` — consume-never-fork, mirroring record-check's identical mapping — so
|
|
159
|
+
* `checkStatusToEventStatus("not_verified")` returns null (no verification event emitted) and the
|
|
160
|
+
* evidence item is stamped `passing:false`. See Decision/finding #2 in the iteration-2 plan.
|
|
161
|
+
*/
|
|
162
|
+
export declare function reduceCaptureLogByCommand(commandLog: AnyObj[] | undefined): Map<string, {
|
|
163
|
+
observedResult: "pass" | "fail" | "ambiguous";
|
|
164
|
+
exitCode: number | null;
|
|
165
|
+
}>;
|
|
136
166
|
/**
|
|
137
167
|
* Build a Hachure trust.bundle from raw check/criterion/critique inputs.
|
|
138
168
|
* trust.bundle is the PRIMARY artifact (ADR 0010 Phase 4a producer inversion).
|
|
@@ -167,7 +197,7 @@ export declare function writeTrustBundle(dir: string, slug: string, timestamp: s
|
|
|
167
197
|
export declare function sidecarBase(slug: string): AnyObj;
|
|
168
198
|
export declare function validateEvidenceRef(ref: AnyObj, label: string): AnyObj;
|
|
169
199
|
export declare function normalizeEvidenceRefs(raw: unknown, label: string): AnyObj[];
|
|
170
|
-
export declare function normalizeCheck(raw: AnyObj): AnyObj;
|
|
200
|
+
export declare function normalizeCheck(raw: AnyObj, allowGateClaimPrefix?: boolean, existingCheckStampById?: ReadonlyMap<string, boolean>): AnyObj;
|
|
171
201
|
/**
|
|
172
202
|
* Derive kit identity from a parsed trust.bundle by structurally reading the
|
|
173
203
|
* DECLARED primary claim (kit-typed) rather than hardcoding "builder".
|
|
@@ -252,6 +282,34 @@ export declare class NotFreshHolderError extends Error {
|
|
|
252
282
|
guidance: string[];
|
|
253
283
|
});
|
|
254
284
|
}
|
|
285
|
+
/**
|
|
286
|
+
* Distinct, identifiable error for a publish attempt whose resolved `repoRoot` does not
|
|
287
|
+
* positively confirm the session's sealed `trust.checkpoint.json` `commit_sha` — a FOURTH,
|
|
288
|
+
* distinct fail-closed tier (#413 Facet A), never conflated with `InvalidBundleShapeError`
|
|
289
|
+
* (bundle shape) or `NotFreshHolderError` (actor hold). This is the "refuse loudly when the cwd
|
|
290
|
+
* repo does not match the sealed checkpoint commit_sha" fallback the issue itself proposed:
|
|
291
|
+
* `publishDelivery`'s `repoRoot` is resolved by walking up from the ARTIFACT directory
|
|
292
|
+
* (findRepoRootFromDirStrict), which always physically lives under the primary checkout even
|
|
293
|
+
* when invoked from an unrelated worktree/repo — so a positively-confirmed mismatch here is the
|
|
294
|
+
* signal that this resolved repoRoot is NOT the repo the sealed checkpoint was produced against,
|
|
295
|
+
* and publishing into it would silently write to the wrong tree.
|
|
296
|
+
*
|
|
297
|
+
* #413 iteration-2 Fix 4: thrown for the ONE positively-determinable `mismatchKind` (see
|
|
298
|
+
* checkpointHeadAncestry's doc comment) — `"positive"` (repoRoot's HEAD is confirmed NOT an
|
|
299
|
+
* ancestor-or-equal of commit_sha; `headSha` is a real sha). It is intentionally NEVER thrown for
|
|
300
|
+
* `"undeterminable-shallow"` (e.g. a shallow clone missing the commit object) — that case only
|
|
301
|
+
* warns and allows the publish (see publishDelivery's own gate). A non-git `repoRoot` classifies
|
|
302
|
+
* as `mismatchKind === "none"` (no opinion — allow; see checkpointHeadAncestry's doc comment for
|
|
303
|
+
* why the iteration-2 Fix 5 refusal here was reverted in iteration-3) and never throws this error
|
|
304
|
+
* either.
|
|
305
|
+
*/
|
|
306
|
+
export declare class RepoHeadMismatchError extends Error {
|
|
307
|
+
readonly code: "PUBLISH_DELIVERY_REPO_HEAD_MISMATCH";
|
|
308
|
+
readonly repoRoot: string;
|
|
309
|
+
readonly commitSha: string;
|
|
310
|
+
readonly headSha: string;
|
|
311
|
+
constructor(repoRoot: string, commitSha: string, headSha: string);
|
|
312
|
+
}
|
|
255
313
|
export declare function runReconcilePreflight(dir: string, repoRoot: string, manifestOverride?: string | null): {
|
|
256
314
|
ok: boolean;
|
|
257
315
|
issues: string[];
|
|
@@ -439,17 +497,44 @@ export declare function runTakeoverPreflight(dir: string, opts?: {
|
|
|
439
497
|
* which stay exactly as before (see each publishDelivery call site's catch handler for how
|
|
440
498
|
* the distinction is preserved end-to-end).
|
|
441
499
|
*
|
|
442
|
-
* #293 (
|
|
500
|
+
* #293 (THIRD, DISTINCT fail-closed gate): immediately after the shape check above and BEFORE
|
|
443
501
|
* writing anything into delivery/, runs `runVerifyHold()` — the assignment ⋈ liveness join
|
|
444
502
|
* asking "is the calling actor the fresh, non-superseded holder of this subject (or is it
|
|
445
503
|
* free/self-held)?" A not-fresh-holder result throws `NotFreshHolderError` (distinct from
|
|
446
504
|
* `InvalidBundleShapeError` — a different `code`, a different failure mode: actor hold vs
|
|
447
|
-
* bundle shape).
|
|
448
|
-
*
|
|
449
|
-
*
|
|
450
|
-
*
|
|
451
|
-
*
|
|
452
|
-
*
|
|
505
|
+
* bundle shape).
|
|
506
|
+
*
|
|
507
|
+
* #413 Facet A (FOURTH, DISTINCT fail-closed gate): immediately after the verify-hold gate and
|
|
508
|
+
* BEFORE writing anything into delivery/, compares the resolved `repoRoot`'s `git rev-parse
|
|
509
|
+
* HEAD` against the session's sealed `trust.checkpoint.json` `commit_sha` (via the shared
|
|
510
|
+
* checkpointHeadAncestry() helper — the SAME ancestor-check checkpointStalenessWarning already
|
|
511
|
+
* used as a non-blocking warning). Throws `RepoHeadMismatchError` (distinct from both other
|
|
512
|
+
* error tiers) instead of silently publishing into what is very likely the WRONG repo (e.g.
|
|
513
|
+
* `repoRoot` resolved to the primary checkout via findRepoRootFromDirStrict's artifact-dir
|
|
514
|
+
* walk, even though this session's checkpoint was sealed against a DIFFERENT worktree's HEAD).
|
|
515
|
+
* A session with no checkpoint yet, or a checkpoint with no commit_sha, has nothing to compare
|
|
516
|
+
* against and is unaffected (scoped strictly to the mismatch case, never a spurious refusal).
|
|
517
|
+
*
|
|
518
|
+
* #413 iteration-2 Fix 4 (see checkpointHeadAncestry's own doc comment for the full
|
|
519
|
+
* `mismatchKind` classification this gate reads): only `mismatchKind === "positive"` (a
|
|
520
|
+
* POSITIVELY-DETERMINABLE ancestry mismatch — the real wrong-tree harm case) hard refuses here.
|
|
521
|
+
* `"undeterminable-shallow"` (e.g. a local shallow clone missing the commit_sha object) must
|
|
522
|
+
* NEVER hard-refuse a legitimate publish — this branch only WARNS loudly and lets the shape +
|
|
523
|
+
* verify-hold gates above continue to stand guard. A non-git `repoRoot` classifies as
|
|
524
|
+
* `mismatchKind === "none"` (allow) — iteration-2's Fix 5 hard-refused this case too, but that
|
|
525
|
+
* regressed the SUPPORTED non-git-repoRoot scratch/checkpoint-signing flow (see
|
|
526
|
+
* evals/integration/test_checkpoint_signing.sh TEST 2) and was reverted in iteration-3. Wrong-tree
|
|
527
|
+
* protection for that case relies on the shape-check and verify-hold gates above, which correctly
|
|
528
|
+
* no-op (allow) when there is no git repo to inspect, same as this gate.
|
|
529
|
+
*
|
|
530
|
+
* There are now FOUR fail-closed/fail-soft TIERS in this function, and they must never be
|
|
531
|
+
* conflated in prose or in a call site's catch handler: (1) fail-SOFT bundle absence /
|
|
532
|
+
* repo-root resolution (silent no-op / visible warning, unchanged from before #356), (2)
|
|
533
|
+
* fail-CLOSED bundle shape (#356, `InvalidBundleShapeError`), (3) fail-CLOSED verify-hold (#293,
|
|
534
|
+
* `NotFreshHolderError`), (4) fail-CLOSED repo-HEAD-vs-checkpoint mismatch (#413 Facet A,
|
|
535
|
+
* `RepoHeadMismatchError`) — see this tier's own gate below for the undeterminable-shallow
|
|
536
|
+
* carve-out. Tiers run in this fixed order (shape, then verify-hold, then HEAD-mismatch); a
|
|
537
|
+
* bundle that fails an earlier tier throws that tier's error specifically, never a later one's.
|
|
453
538
|
*/
|
|
454
539
|
export declare function publishDelivery(dir: string, repoRoot: string | null): Promise<void>;
|
|
455
540
|
export declare function validateLearningCorrection(record: AnyObj): void;
|