@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,575 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# test_liveness_worktree_root.sh -- integration eval for #357: the shared, git-common-dir-aware
|
|
3
|
+
# artifact-root resolver (flowAgentsArtifactRoot / defaultArtifactRootForRead, under
|
|
4
|
+
# src/lib/ in the main tree, and the CJS hook twin under scripts/hooks/lib/ -- see
|
|
5
|
+
# local-artifact-root.ts and local-artifact-paths.js).
|
|
6
|
+
#
|
|
7
|
+
# Uses a REAL `git worktree add` linked worktree (not a simulated divergent path -- the plan's
|
|
8
|
+
# own stop-short-risk note explicitly requires this, since `git rev-parse --git-common-dir`
|
|
9
|
+
# behaves identically to `--absolute-git-dir` outside a real linked worktree, so a simulated
|
|
10
|
+
# two-unrelated-repos setup would never actually exercise the git-common-dir resolution code
|
|
11
|
+
# path at all).
|
|
12
|
+
#
|
|
13
|
+
# Covers:
|
|
14
|
+
# AC3 liveness-claim-from-worktree-shared-store: `liveness claim` invoked with cwd inside the
|
|
15
|
+
# linked worktree (no --artifact-root) writes into the SAME liveness stream a
|
|
16
|
+
# `liveness status` invocation from the PRIMARY checkout sees.
|
|
17
|
+
# AC4 ensure-session-ownership-guard-shared-store: a claim held (via ensure-session) in the
|
|
18
|
+
# PRIMARY checkout is recognized by `ensure-session`'s ownership guard when invoked for the
|
|
19
|
+
# SAME subject from the linked worktree's cwd -- the guard must refuse (see the existing hold),
|
|
20
|
+
# never silently proceed as if the subject were free.
|
|
21
|
+
# AC5 current-pointer-shared-store-from-worktree: `ensure-session`'s current.json write (from
|
|
22
|
+
# the worktree cwd, no --artifact-root) is visible to `workflow-sidecar current` invoked from
|
|
23
|
+
# the PRIMARY checkout cwd (both resolve the SAME shared .kontourai/flow-agents root).
|
|
24
|
+
# AC6 single-checkout-backward-compat (this eval's own contribution): byte-identical to
|
|
25
|
+
# before this change ONLY for the two cases where the pre-#357 code and the new
|
|
26
|
+
# git-common-dir resolver necessarily agree: (a) cwd is the repo ROOT of a plain
|
|
27
|
+
# (non-worktree) checkout, and (b) cwd is not inside a git repo at all (fail-open). Both are
|
|
28
|
+
# asserted below with a companion case proving byte-identical path.resolve(cwd,
|
|
29
|
+
# ".kontourai/flow-agents") output.
|
|
30
|
+
# AC6b subdirectory-resolves-to-shared-repo-root (#413 iteration-2 Fix 2, INTENDED CHANGE, not
|
|
31
|
+
# byte-identical): when cwd is a SUBDIRECTORY of a plain (non-worktree) checkout, the OLD
|
|
32
|
+
# code anchored the store to that subdir (path.resolve(cwd, ...)), but `git rev-parse
|
|
33
|
+
# --git-common-dir` from a subdirectory returns a RELATIVE path this resolver walks up to the
|
|
34
|
+
# repo root, so flowAgentsArtifactRoot now correctly resolves to
|
|
35
|
+
# <repo-root>/.kontourai/flow-agents regardless of which subdirectory cwd is -- ONE shared
|
|
36
|
+
# store per repo, matching every other cwd (primary checkout, linked worktree) this resolver
|
|
37
|
+
# already unifies. This is intentional, correct shared-store behavior, NOT a regression to
|
|
38
|
+
# revert -- asserted explicitly below so it stays a documented, tested behavior change
|
|
39
|
+
# rather than an unverified side effect of the git-common-dir walk.
|
|
40
|
+
#
|
|
41
|
+
# Deterministic, no model spend, self-cleaning (mktemp -d + trap EXIT). The worktree fixture is
|
|
42
|
+
# entirely disposable, created and torn down inside this eval's own scratch directory -- it never
|
|
43
|
+
# touches this session's real worktree (see the plan's Sandbox mode note).
|
|
44
|
+
#
|
|
45
|
+
# Usage: bash evals/integration/test_liveness_worktree_root.sh
|
|
46
|
+
|
|
47
|
+
set -uo pipefail
|
|
48
|
+
|
|
49
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
50
|
+
source "$ROOT/evals/lib/node.sh"
|
|
51
|
+
|
|
52
|
+
WRITER="workflow-sidecar"
|
|
53
|
+
TMP="$(mktemp -d)"
|
|
54
|
+
errors=0
|
|
55
|
+
|
|
56
|
+
_pass() { echo " PASS: $1"; }
|
|
57
|
+
_fail() { echo " FAIL: $1"; errors=$((errors + 1)); }
|
|
58
|
+
|
|
59
|
+
cleanup() { rm -rf "$TMP"; }
|
|
60
|
+
trap cleanup EXIT
|
|
61
|
+
|
|
62
|
+
if ! command -v git >/dev/null 2>&1; then
|
|
63
|
+
echo "git is not installed -- cannot exercise a real 'git worktree add' fixture." >&2
|
|
64
|
+
echo "ACCEPTED GAP: this eval requires git; recording as an explicit, non-silent skip (not a pass)." >&2
|
|
65
|
+
exit 1
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
flow_agents_build_ts || { echo "build failed" >&2; exit 1; }
|
|
69
|
+
|
|
70
|
+
# ==== SETUP: a real primary checkout + a real linked worktree ======================
|
|
71
|
+
echo ""
|
|
72
|
+
echo "=== SETUP: primary checkout + real 'git worktree add' linked worktree ==="
|
|
73
|
+
|
|
74
|
+
PRIMARY="$TMP/primary"
|
|
75
|
+
mkdir -p "$PRIMARY/kits"
|
|
76
|
+
(
|
|
77
|
+
cd "$PRIMARY" \
|
|
78
|
+
&& git init -q \
|
|
79
|
+
&& git config user.email "test@test.local" \
|
|
80
|
+
&& git config user.name "test" \
|
|
81
|
+
&& printf '# marker\n' > kits/.keep \
|
|
82
|
+
&& git add -A \
|
|
83
|
+
&& git commit -q -m "init"
|
|
84
|
+
)
|
|
85
|
+
if [[ -d "$PRIMARY/.git" ]]; then
|
|
86
|
+
_pass "SETUP: primary checkout is a real git repo with a kits/ marker"
|
|
87
|
+
else
|
|
88
|
+
_fail "SETUP: primary checkout git init failed"
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
WORKTREE="$TMP/linked-worktree"
|
|
92
|
+
if (cd "$PRIMARY" && git worktree add -q -b worktree-branch "$WORKTREE" >/dev/null 2>"$TMP/worktree-add.err"); then
|
|
93
|
+
_pass "SETUP: 'git worktree add' created a genuine linked worktree"
|
|
94
|
+
else
|
|
95
|
+
_fail "SETUP: 'git worktree add' failed: $(cat "$TMP/worktree-add.err")"
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
# A linked worktree's OWN .git is a FILE (a gitdir: pointer), never a directory -- this is the
|
|
99
|
+
# exact condition the pre-fix cwd/`.git`-ancestor-walk resolvers could not see past.
|
|
100
|
+
if [[ -f "$WORKTREE/.git" && ! -d "$WORKTREE/.git" ]]; then
|
|
101
|
+
_pass "SETUP: linked worktree's own .git is a FILE, not a directory (the condition this fix targets)"
|
|
102
|
+
else
|
|
103
|
+
_fail "SETUP: linked worktree's .git was not the expected gitdir-pointer FILE"
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
# Confirm the git primitive itself resolves the worktree to the PRIMARY checkout's real .git dir
|
|
107
|
+
# -- this is the exact resolution flowAgentsArtifactRoot()/defaultArtifactRootForRead() now use.
|
|
108
|
+
COMMON_DIR="$(cd "$WORKTREE" && git rev-parse --git-common-dir 2>/dev/null)"
|
|
109
|
+
if [[ -n "$COMMON_DIR" ]]; then
|
|
110
|
+
ABS_COMMON_DIR="$(cd "$WORKTREE" && node -e 'console.log(require("path").resolve(process.argv[1], process.argv[2]))' "$WORKTREE" "$COMMON_DIR")"
|
|
111
|
+
RESOLVED_SHARED_ROOT="$(dirname "$ABS_COMMON_DIR")"
|
|
112
|
+
REAL_PRIMARY="$(cd "$PRIMARY" && pwd -P)"
|
|
113
|
+
REAL_RESOLVED="$(cd "$RESOLVED_SHARED_ROOT" && pwd -P)"
|
|
114
|
+
if [[ "$REAL_RESOLVED" == "$REAL_PRIMARY" ]]; then
|
|
115
|
+
_pass "SETUP: 'git rev-parse --git-common-dir' from the worktree resolves to the PRIMARY checkout's repo root"
|
|
116
|
+
else
|
|
117
|
+
_fail "SETUP: git-common-dir resolution did not land on the primary checkout: got $REAL_RESOLVED, expected $REAL_PRIMARY"
|
|
118
|
+
fi
|
|
119
|
+
else
|
|
120
|
+
_fail "SETUP: 'git rev-parse --git-common-dir' failed from the linked worktree"
|
|
121
|
+
fi
|
|
122
|
+
|
|
123
|
+
# ==== AC3: liveness claim from worktree cwd is visible from the primary checkout ====
|
|
124
|
+
echo ""
|
|
125
|
+
echo "=== AC3: liveness claim (worktree cwd, no --artifact-root) visible from primary checkout ==="
|
|
126
|
+
|
|
127
|
+
SUBJECT="worktree-liveness-subject"
|
|
128
|
+
if (cd "$WORKTREE" && flow_agents_node "$WRITER" liveness claim "$SUBJECT" --actor worktree-actor \
|
|
129
|
+
>"$TMP/claim.out" 2>"$TMP/claim.err"); then
|
|
130
|
+
_pass "AC3: liveness claim from the worktree cwd (no --artifact-root) exits 0"
|
|
131
|
+
else
|
|
132
|
+
_fail "AC3: liveness claim from the worktree cwd failed: $(cat "$TMP/claim.out" "$TMP/claim.err")"
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
# The claim must land in the PRIMARY checkout's .kontourai/flow-agents/liveness store, NOT an
|
|
136
|
+
# isolated .kontourai/flow-agents under the worktree's own directory.
|
|
137
|
+
if [[ -f "$PRIMARY/.kontourai/flow-agents/liveness/events.jsonl" ]]; then
|
|
138
|
+
_pass "AC3: the claim landed in the PRIMARY checkout's shared .kontourai/flow-agents/liveness store"
|
|
139
|
+
else
|
|
140
|
+
_fail "AC3: no liveness/events.jsonl found under the primary checkout's .kontourai/flow-agents"
|
|
141
|
+
fi
|
|
142
|
+
if [[ ! -e "$WORKTREE/.kontourai/flow-agents/liveness/events.jsonl" ]]; then
|
|
143
|
+
_pass "AC3: no ISOLATED per-worktree .kontourai/flow-agents/liveness store was created under the worktree"
|
|
144
|
+
else
|
|
145
|
+
_fail "AC3: an isolated per-worktree liveness store was created -- the shared-root fix did not take effect"
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
if (cd "$PRIMARY" && flow_agents_node "$WRITER" liveness status --subject "$SUBJECT" --json \
|
|
149
|
+
>"$TMP/status.out" 2>"$TMP/status.err"); then
|
|
150
|
+
_pass "AC3: liveness status from the PRIMARY checkout cwd (no --artifact-root) exits 0"
|
|
151
|
+
else
|
|
152
|
+
_fail "AC3: liveness status from the primary checkout failed: $(cat "$TMP/status.out" "$TMP/status.err")"
|
|
153
|
+
fi
|
|
154
|
+
if grep -qF "worktree-actor" "$TMP/status.out"; then
|
|
155
|
+
_pass "AC3: a reader in the PRIMARY checkout sees the claim made from the linked worktree's cwd"
|
|
156
|
+
else
|
|
157
|
+
_fail "AC3: the primary checkout's liveness status did not see the worktree-made claim: $(cat "$TMP/status.out")"
|
|
158
|
+
fi
|
|
159
|
+
|
|
160
|
+
# ==== AC4: ensure-session's ownership guard recognizes a primary-checkout hold from worktree cwd
|
|
161
|
+
echo ""
|
|
162
|
+
echo "=== AC4: ensure-session ownership guard sees a primary-checkout hold from worktree cwd ==="
|
|
163
|
+
|
|
164
|
+
GUARD_WORK_ITEM="kontourai/flow-agents#8801"
|
|
165
|
+
GUARD_SLUG="kontourai-flow-agents-8801"
|
|
166
|
+
|
|
167
|
+
if (cd "$PRIMARY" && flow_agents_node "$WRITER" ensure-session \
|
|
168
|
+
--work-item "$GUARD_WORK_ITEM" \
|
|
169
|
+
--actor primary-holder \
|
|
170
|
+
--source-request "Primary-checkout actor claims a free subject." \
|
|
171
|
+
--summary "Establish the hold in the primary checkout." \
|
|
172
|
+
>"$TMP/guard-primary.out" 2>"$TMP/guard-primary.err"); then
|
|
173
|
+
_pass "AC4 setup: ensure-session from the primary checkout establishes a durable claim"
|
|
174
|
+
else
|
|
175
|
+
_fail "AC4 setup: ensure-session from the primary checkout failed: $(cat "$TMP/guard-primary.out" "$TMP/guard-primary.err")"
|
|
176
|
+
fi
|
|
177
|
+
|
|
178
|
+
# A fresh liveness claim event for the SAME actor/subject, so the join classifies this `held`
|
|
179
|
+
# (self_is_holder / fresh_liveness_heartbeat) rather than merely `reclaimable` -- matches
|
|
180
|
+
# test_ensure_session_ownership_guard.sh's own convention of backing the assignment record with
|
|
181
|
+
# a real liveness event before asserting a second actor's refusal.
|
|
182
|
+
(cd "$PRIMARY" && flow_agents_node "$WRITER" liveness claim "$GUARD_SLUG" --actor primary-holder \
|
|
183
|
+
>"$TMP/guard-liveness-claim.out" 2>"$TMP/guard-liveness-claim.err") || true
|
|
184
|
+
|
|
185
|
+
if (cd "$WORKTREE" && flow_agents_node "$WRITER" ensure-session \
|
|
186
|
+
--work-item "$GUARD_WORK_ITEM" \
|
|
187
|
+
--actor worktree-other-actor \
|
|
188
|
+
--source-request "A different actor attempts entry from the linked worktree's cwd." \
|
|
189
|
+
--summary "Should be refused -- the primary checkout's hold must be visible here too." \
|
|
190
|
+
>"$TMP/guard-worktree.out" 2>"$TMP/guard-worktree.err"); then
|
|
191
|
+
_fail "AC4: ensure-session from the worktree cwd should have been refused (primary checkout's hold is fresh) but exited 0"
|
|
192
|
+
else
|
|
193
|
+
_pass "AC4: ensure-session from the worktree cwd refuses -- it sees the primary checkout's fresh hold (shared store, not isolated)"
|
|
194
|
+
fi
|
|
195
|
+
if grep -qF "primary-holder" "$TMP/guard-worktree.err"; then
|
|
196
|
+
_pass "AC4: the refusal from the worktree cwd names the primary checkout's holder identity"
|
|
197
|
+
else
|
|
198
|
+
_fail "AC4: refusal message did not name the primary checkout's holder identity: $(cat "$TMP/guard-worktree.err")"
|
|
199
|
+
fi
|
|
200
|
+
|
|
201
|
+
# ==== AC5: current.json (per-actor + legacy) written from worktree cwd is visible from primary =
|
|
202
|
+
echo ""
|
|
203
|
+
echo "=== AC5: current.json written from worktree cwd is visible from the primary checkout ==="
|
|
204
|
+
|
|
205
|
+
CURRENT_WORK_ITEM="kontourai/flow-agents#8802"
|
|
206
|
+
CURRENT_SLUG="kontourai-flow-agents-8802"
|
|
207
|
+
|
|
208
|
+
if (cd "$WORKTREE" && flow_agents_node "$WRITER" ensure-session \
|
|
209
|
+
--work-item "$CURRENT_WORK_ITEM" \
|
|
210
|
+
--actor worktree-current-actor \
|
|
211
|
+
--source-request "ensure-session from the worktree cwd writes current.json." \
|
|
212
|
+
--summary "Prove current.json lands in the shared store." \
|
|
213
|
+
>"$TMP/current-worktree.out" 2>"$TMP/current-worktree.err"); then
|
|
214
|
+
_pass "AC5 setup: ensure-session from the worktree cwd (no --artifact-root) exits 0"
|
|
215
|
+
else
|
|
216
|
+
_fail "AC5 setup: ensure-session from the worktree cwd failed: $(cat "$TMP/current-worktree.out" "$TMP/current-worktree.err")"
|
|
217
|
+
fi
|
|
218
|
+
|
|
219
|
+
if [[ -f "$PRIMARY/.kontourai/flow-agents/current.json" ]]; then
|
|
220
|
+
_pass "AC5: current.json landed under the PRIMARY checkout's shared .kontourai/flow-agents, not an isolated worktree copy"
|
|
221
|
+
else
|
|
222
|
+
_fail "AC5: no current.json found under the primary checkout's .kontourai/flow-agents"
|
|
223
|
+
fi
|
|
224
|
+
if [[ ! -e "$WORKTREE/.kontourai/flow-agents/current.json" ]]; then
|
|
225
|
+
_pass "AC5: no isolated per-worktree current.json was created under the worktree"
|
|
226
|
+
else
|
|
227
|
+
_fail "AC5: an isolated per-worktree current.json was created -- the shared-root fix did not take effect"
|
|
228
|
+
fi
|
|
229
|
+
|
|
230
|
+
if (cd "$PRIMARY" && flow_agents_node "$WRITER" current --format slug \
|
|
231
|
+
>"$TMP/current-primary.out" 2>"$TMP/current-primary.err"); then
|
|
232
|
+
if [[ "$(cat "$TMP/current-primary.out")" == "$CURRENT_SLUG" ]]; then
|
|
233
|
+
_pass "AC5: 'current' invoked from the PRIMARY checkout cwd (no --artifact-root) resolves the session ensure-session wrote from the worktree cwd"
|
|
234
|
+
else
|
|
235
|
+
_fail "AC5: primary-checkout 'current' resolved the wrong slug: $(cat "$TMP/current-primary.out"), expected $CURRENT_SLUG"
|
|
236
|
+
fi
|
|
237
|
+
else
|
|
238
|
+
_fail "AC5: 'current' from the primary checkout failed: $(cat "$TMP/current-primary.out" "$TMP/current-primary.err")"
|
|
239
|
+
fi
|
|
240
|
+
|
|
241
|
+
# stop-goal-fit.js / workflow-steering.js's own findRepoRoot (updated to prefer
|
|
242
|
+
# git-common-dir resolution) must ALSO resolve the primary checkout when invoked with cwd set
|
|
243
|
+
# to the LINKED WORKTREE -- proving the hook-side mirrors got the same fix, not just the CLI.
|
|
244
|
+
HOOK_ROOT_CHECK="$(node -e '
|
|
245
|
+
const { findRepoRoot } = require(process.argv[1]);
|
|
246
|
+
console.log(findRepoRoot(process.argv[2]));
|
|
247
|
+
' "$ROOT/scripts/hooks/stop-goal-fit.js" "$WORKTREE" 2>/dev/null)"
|
|
248
|
+
REAL_PRIMARY2="$(cd "$PRIMARY" && pwd -P)"
|
|
249
|
+
if [[ -n "$HOOK_ROOT_CHECK" ]]; then
|
|
250
|
+
REAL_HOOK_ROOT="$(cd "$HOOK_ROOT_CHECK" && pwd -P)"
|
|
251
|
+
if [[ "$REAL_HOOK_ROOT" == "$REAL_PRIMARY2" ]]; then
|
|
252
|
+
_pass "AC5: stop-goal-fit.js's findRepoRoot(worktreeCwd) resolves the shared PRIMARY checkout root, not the worktree's own directory"
|
|
253
|
+
else
|
|
254
|
+
_fail "AC5: stop-goal-fit.js's findRepoRoot(worktreeCwd) did not resolve the primary checkout: got $REAL_HOOK_ROOT, expected $REAL_PRIMARY2"
|
|
255
|
+
fi
|
|
256
|
+
else
|
|
257
|
+
_fail "AC5: stop-goal-fit.js's findRepoRoot(worktreeCwd) returned nothing"
|
|
258
|
+
fi
|
|
259
|
+
|
|
260
|
+
HOOK_ROOT_CHECK_STEERING="$(node -e '
|
|
261
|
+
const { findRepoRoot } = require(process.argv[1]);
|
|
262
|
+
console.log(findRepoRoot(process.argv[2]));
|
|
263
|
+
' "$ROOT/scripts/hooks/workflow-steering.js" "$WORKTREE" 2>/dev/null)"
|
|
264
|
+
if [[ -n "$HOOK_ROOT_CHECK_STEERING" ]]; then
|
|
265
|
+
REAL_HOOK_ROOT_STEERING="$(cd "$HOOK_ROOT_CHECK_STEERING" && pwd -P)"
|
|
266
|
+
if [[ "$REAL_HOOK_ROOT_STEERING" == "$REAL_PRIMARY2" ]]; then
|
|
267
|
+
_pass "AC5: workflow-steering.js's findRepoRoot(worktreeCwd) resolves the shared PRIMARY checkout root, not the worktree's own directory"
|
|
268
|
+
else
|
|
269
|
+
_fail "AC5: workflow-steering.js's findRepoRoot(worktreeCwd) did not resolve the primary checkout: got $REAL_HOOK_ROOT_STEERING, expected $REAL_PRIMARY2"
|
|
270
|
+
fi
|
|
271
|
+
else
|
|
272
|
+
_fail "AC5: workflow-steering.js's findRepoRoot(worktreeCwd) returned nothing"
|
|
273
|
+
fi
|
|
274
|
+
|
|
275
|
+
# ==== FIX 1 (#413 iteration-2, HIGH security): loud, not silent, fail-open inside a broken git
|
|
276
|
+
# working tree ==============================================================
|
|
277
|
+
# A SECOND, disposable linked worktree, deliberately corrupted (its own .git gitlink pointer
|
|
278
|
+
# rewritten to a nonexistent path) AFTER creation, so `git rev-parse --git-common-dir` from
|
|
279
|
+
# inside it genuinely FAILS (git resolution attempted, not merely absent) while the directory is
|
|
280
|
+
# still, by every other measure, inside a git working tree (a `.git` FILE exists right there).
|
|
281
|
+
# This is #357's exact silent-fail-open bug: prior to Fix 1, flowAgentsArtifactRoot/findRepoRoot
|
|
282
|
+
# would fall back to a cwd-local store with NO diagnostic at all. Uses its own worktree fixture
|
|
283
|
+
# (never $WORKTREE, which later AC sections below still depend on being intact).
|
|
284
|
+
echo ""
|
|
285
|
+
echo "=== FIX 1: loud WARNING when inside a git working tree but resolution fails (corrupted gitlink) ==="
|
|
286
|
+
|
|
287
|
+
BROKEN_WORKTREE="$TMP/broken-worktree"
|
|
288
|
+
if (cd "$PRIMARY" && git worktree add -q -b broken-worktree-branch "$BROKEN_WORKTREE" >/dev/null 2>"$TMP/broken-worktree-add.err"); then
|
|
289
|
+
_pass "FIX 1 setup: a second, disposable linked worktree was created for the corruption fixture"
|
|
290
|
+
else
|
|
291
|
+
_fail "FIX 1 setup: 'git worktree add' for the broken-worktree fixture failed: $(cat "$TMP/broken-worktree-add.err")"
|
|
292
|
+
fi
|
|
293
|
+
|
|
294
|
+
# Corrupt the gitlink: a linked worktree's own .git is normally a FILE containing
|
|
295
|
+
# `gitdir: <path-to-real-.git-entry>`. Point it at a nonexistent path so git-common-dir
|
|
296
|
+
# resolution genuinely fails from inside this worktree, while a `.git` FILE still exists here
|
|
297
|
+
# (the exact condition isInsideGitWorkingTree's discriminator detects).
|
|
298
|
+
printf 'gitdir: /nonexistent-gitdir-for-fix1-eval
|
|
299
|
+
' > "$BROKEN_WORKTREE/.git"
|
|
300
|
+
|
|
301
|
+
BROKEN_COMMON_DIR_CHECK="$(cd "$BROKEN_WORKTREE" && git rev-parse --git-common-dir 2>/dev/null)"
|
|
302
|
+
if [[ -z "$BROKEN_COMMON_DIR_CHECK" ]]; then
|
|
303
|
+
_pass "FIX 1 setup: 'git rev-parse --git-common-dir' genuinely fails from inside the corrupted worktree"
|
|
304
|
+
else
|
|
305
|
+
_fail "FIX 1 setup: corruption did not take effect -- git-common-dir still resolved: $BROKEN_COMMON_DIR_CHECK"
|
|
306
|
+
fi
|
|
307
|
+
|
|
308
|
+
if (cd "$BROKEN_WORKTREE" && flow_agents_node "$WRITER" liveness claim "fix1-corrupted-gitlink-subject" --actor fix1-actor \
|
|
309
|
+
>"$TMP/fix1-claim.out" 2>"$TMP/fix1-claim.err"); then
|
|
310
|
+
_pass "FIX 1: liveness claim from the corrupted worktree still exits 0 (loud warning, not a hard refuse -- this helper sits on read paths too)"
|
|
311
|
+
else
|
|
312
|
+
_fail "FIX 1: liveness claim from the corrupted worktree unexpectedly failed (should warn and fall back, not refuse): $(cat "$TMP/fix1-claim.out" "$TMP/fix1-claim.err")"
|
|
313
|
+
fi
|
|
314
|
+
|
|
315
|
+
if grep -q "\[artifact-root\] WARNING: inside a git working tree but could not resolve the shared repo root" "$TMP/fix1-claim.err"; then
|
|
316
|
+
_pass "FIX 1: a LOUD [artifact-root] WARNING was emitted to stderr for the corrupted-gitlink fail-open"
|
|
317
|
+
else
|
|
318
|
+
_fail "FIX 1: expected a loud [artifact-root] WARNING on stderr, got: $(cat "$TMP/fix1-claim.err")"
|
|
319
|
+
fi
|
|
320
|
+
|
|
321
|
+
# The fallback must still be a cwd-local store under the (corrupted) worktree itself -- the
|
|
322
|
+
# warning is loud, but the actual fail-open target is unchanged from #357's baseline behavior.
|
|
323
|
+
if [[ -f "$BROKEN_WORKTREE/.kontourai/flow-agents/liveness/events.jsonl" ]]; then
|
|
324
|
+
_pass "FIX 1: the fallback store landed at the cwd-local (corrupted worktree) path, as documented"
|
|
325
|
+
else
|
|
326
|
+
_fail "FIX 1: no cwd-local fallback store found under the corrupted worktree after the loud warning"
|
|
327
|
+
fi
|
|
328
|
+
|
|
329
|
+
# Mutation-test note: deleting the isInsideGitWorkingTree() check (i.e. warning unconditionally,
|
|
330
|
+
# even for a cwd with no git repo at all) would make the AC6 non-git companion case below FAIL,
|
|
331
|
+
# since that case asserts pure, silent fail-open with no stderr expectations. Deleting the
|
|
332
|
+
# warnIfFailingOpenInsideGitTree() call entirely (i.e. reverting to pure silent fail-open) would
|
|
333
|
+
# make the grep assertion immediately above this note FAIL.
|
|
334
|
+
|
|
335
|
+
# ==== FIX 1b: a bad ambient GIT_DIR must resolve SILENTLY (proves Fix 3 runs BEFORE Fix 1's
|
|
336
|
+
# warning check) =============================================================================
|
|
337
|
+
# A nonexistent-path GIT_DIR, set as an AMBIENT env var, from a cwd that IS genuinely inside a
|
|
338
|
+
# real, uncorrupted git working tree (the PRIMARY checkout). Fix 3 (env-stripping) means this
|
|
339
|
+
# ambient GIT_DIR is deleted from the env BEFORE the internal `git rev-parse --git-common-dir`
|
|
340
|
+
# shell-out runs, so resolution succeeds normally here -- NO warning, NO fail-open at all. This
|
|
341
|
+
# is an important companion to FIX 1's own gitlink-corruption repro above: it proves Fix 1's loud
|
|
342
|
+
# warning is reserved for GENUINE resolution failures (corrupted .git, git unusable, etc.), not
|
|
343
|
+
# merely "some ambient git env var looked suspicious" -- an ambient GIT_DIR alone must never, by
|
|
344
|
+
# itself, cause a loud warning once Fix 3 has stripped it.
|
|
345
|
+
echo ""
|
|
346
|
+
echo "=== FIX 1b: an ambient (nonexistent) GIT_DIR resolves SILENTLY -- Fix 3's stripping runs first, no false-positive warning ==="
|
|
347
|
+
|
|
348
|
+
if (cd "$PRIMARY" && GIT_DIR=/nonexistent-git-dir-for-fix1b-eval flow_agents_node "$WRITER" liveness claim "fix1b-baddir-subject" --actor fix1b-actor \
|
|
349
|
+
>"$TMP/fix1b-claim.out" 2>"$TMP/fix1b-claim.err"); then
|
|
350
|
+
_pass "FIX 1b: liveness claim with an ambient (nonexistent) GIT_DIR exits 0"
|
|
351
|
+
else
|
|
352
|
+
_fail "FIX 1b: liveness claim with an ambient GIT_DIR unexpectedly failed: $(cat "$TMP/fix1b-claim.out" "$TMP/fix1b-claim.err")"
|
|
353
|
+
fi
|
|
354
|
+
|
|
355
|
+
if grep -q "\[artifact-root\] WARNING" "$TMP/fix1b-claim.err"; then
|
|
356
|
+
_fail "FIX 1b: unexpectedly emitted an [artifact-root] WARNING for a merely-ambient (stripped) GIT_DIR -- Fix 3's env-stripping regressed, or Fix 1 is firing as a false positive: $(cat "$TMP/fix1b-claim.err")"
|
|
357
|
+
else
|
|
358
|
+
_pass "FIX 1b: no [artifact-root] WARNING was emitted -- the ambient GIT_DIR was correctly stripped before resolution (Fix 3), so Fix 1's warning never had a genuine failure to react to"
|
|
359
|
+
fi
|
|
360
|
+
|
|
361
|
+
if [[ -f "$PRIMARY/.kontourai/flow-agents/liveness/events.jsonl" ]] && grep -qF "fix1b-actor" "$PRIMARY/.kontourai/flow-agents/liveness/events.jsonl"; then
|
|
362
|
+
_pass "FIX 1b: the claim landed in PRIMARY's own shared store, resolution was not disrupted by the ambient GIT_DIR"
|
|
363
|
+
else
|
|
364
|
+
_fail "FIX 1b: the claim did not land in PRIMARY's own shared store as expected"
|
|
365
|
+
fi
|
|
366
|
+
|
|
367
|
+
# ==== FIX 1c: loud WARNING via a SECOND, distinct real corruption vector (unreadable .git/HEAD)
|
|
368
|
+
# =============================================================================================
|
|
369
|
+
# A DIFFERENT genuine git-resolution-failure vector from FIX 1's gitlink corruption above:
|
|
370
|
+
# an unreadable .git/HEAD inside yet another disposable worktree. Confirms the loud warning is
|
|
371
|
+
# not narrowly coupled to the specific "gitdir: pointer" corruption shape -- ANY genuine
|
|
372
|
+
# `git rev-parse --git-common-dir` failure from inside a real working tree triggers it. Skips
|
|
373
|
+
# gracefully (not a silent pass) when running as root, where chmod 000 does not restrict access.
|
|
374
|
+
echo ""
|
|
375
|
+
echo "=== FIX 1c: loud WARNING via a second, distinct corruption vector (unreadable .git/HEAD) ==="
|
|
376
|
+
|
|
377
|
+
if [[ "$(id -u)" == "0" ]]; then
|
|
378
|
+
echo " SKIP: running as root -- chmod 000 does not restrict access, cannot exercise this vector here."
|
|
379
|
+
else
|
|
380
|
+
PERM_WORKTREE="$TMP/perm-broken-worktree"
|
|
381
|
+
if (cd "$PRIMARY" && git worktree add -q -b perm-broken-worktree-branch "$PERM_WORKTREE" >/dev/null 2>"$TMP/perm-broken-worktree-add.err"); then
|
|
382
|
+
_pass "FIX 1c setup: a third, disposable linked worktree was created for the permission-corruption fixture"
|
|
383
|
+
else
|
|
384
|
+
_fail "FIX 1c setup: 'git worktree add' for the perm-broken-worktree fixture failed: $(cat "$TMP/perm-broken-worktree-add.err")"
|
|
385
|
+
fi
|
|
386
|
+
|
|
387
|
+
# The linked worktree's real git-dir entry lives under the PRIMARY checkout's
|
|
388
|
+
# .git/worktrees/<name>/ -- corrupt ITS HEAD file (unreadable), which is what
|
|
389
|
+
# `git rev-parse --git-common-dir` from inside PERM_WORKTREE actually needs to read.
|
|
390
|
+
REAL_GITDIR_ENTRY="$(cd "$PERM_WORKTREE" && git rev-parse --git-dir 2>/dev/null)"
|
|
391
|
+
if [[ -n "$REAL_GITDIR_ENTRY" && -f "$REAL_GITDIR_ENTRY/HEAD" ]]; then
|
|
392
|
+
chmod 000 "$REAL_GITDIR_ENTRY/HEAD"
|
|
393
|
+
_pass "FIX 1c setup: made the linked worktree's own HEAD file unreadable"
|
|
394
|
+
else
|
|
395
|
+
_fail "FIX 1c setup: could not locate the linked worktree's real git-dir HEAD file to corrupt"
|
|
396
|
+
fi
|
|
397
|
+
|
|
398
|
+
PERM_COMMON_DIR_CHECK="$(cd "$PERM_WORKTREE" && git rev-parse --git-common-dir 2>/dev/null)"
|
|
399
|
+
if [[ -z "$PERM_COMMON_DIR_CHECK" ]]; then
|
|
400
|
+
_pass "FIX 1c setup: 'git rev-parse --git-common-dir' genuinely fails from inside the permission-corrupted worktree"
|
|
401
|
+
else
|
|
402
|
+
_fail "FIX 1c setup: corruption did not take effect -- git-common-dir still resolved: $PERM_COMMON_DIR_CHECK"
|
|
403
|
+
fi
|
|
404
|
+
|
|
405
|
+
if (cd "$PERM_WORKTREE" && flow_agents_node "$WRITER" liveness claim "fix1c-perm-corrupted-subject" --actor fix1c-actor \
|
|
406
|
+
>"$TMP/fix1c-claim.out" 2>"$TMP/fix1c-claim.err"); then
|
|
407
|
+
_pass "FIX 1c: liveness claim from the permission-corrupted worktree still exits 0 (loud warning, not a hard refuse)"
|
|
408
|
+
else
|
|
409
|
+
_fail "FIX 1c: liveness claim from the permission-corrupted worktree unexpectedly failed: $(cat "$TMP/fix1c-claim.out" "$TMP/fix1c-claim.err")"
|
|
410
|
+
fi
|
|
411
|
+
|
|
412
|
+
if grep -q "\[artifact-root\] WARNING: inside a git working tree but could not resolve the shared repo root" "$TMP/fix1c-claim.err"; then
|
|
413
|
+
_pass "FIX 1c: a LOUD [artifact-root] WARNING was emitted to stderr for this second, distinct corruption vector"
|
|
414
|
+
else
|
|
415
|
+
_fail "FIX 1c: expected a loud [artifact-root] WARNING on stderr, got: $(cat "$TMP/fix1c-claim.err")"
|
|
416
|
+
fi
|
|
417
|
+
|
|
418
|
+
# Restore permissions so this eval's own cleanup (rm -rf "$TMP") can succeed.
|
|
419
|
+
chmod 644 "$REAL_GITDIR_ENTRY/HEAD" 2>/dev/null || true
|
|
420
|
+
fi
|
|
421
|
+
|
|
422
|
+
# ==== AC6: single-checkout backward compat -- no worktree at all, path is byte-identical =======
|
|
423
|
+
echo ""
|
|
424
|
+
echo "=== AC6: single-checkout backward compat (no worktree) -- path unchanged from today ==="
|
|
425
|
+
|
|
426
|
+
PLAIN="$TMP/plain-repo"
|
|
427
|
+
mkdir -p "$PLAIN/kits"
|
|
428
|
+
(cd "$PLAIN" && git init -q && git config user.email "test@test.local" && git config user.name "test" \
|
|
429
|
+
&& printf '# marker\n' > kits/.keep && git add -A && git commit -q -m "init")
|
|
430
|
+
|
|
431
|
+
if (cd "$PLAIN" && flow_agents_node "$WRITER" liveness claim "plain-subject" --actor plain-actor \
|
|
432
|
+
>"$TMP/plain-claim.out" 2>"$TMP/plain-claim.err"); then
|
|
433
|
+
_pass "AC6: liveness claim from a plain (non-worktree) git repo cwd exits 0"
|
|
434
|
+
else
|
|
435
|
+
_fail "AC6: liveness claim from a plain git repo cwd failed: $(cat "$TMP/plain-claim.out" "$TMP/plain-claim.err")"
|
|
436
|
+
fi
|
|
437
|
+
# In the single-checkout case, --git-common-dir resolves to .git under cwd itself, so
|
|
438
|
+
# path.dirname() of its absolute form is cwd -- the resolved root MUST be exactly
|
|
439
|
+
# <PLAIN>/.kontourai/flow-agents, byte-for-byte the same as plain path.resolve(cwd, ...).
|
|
440
|
+
if [[ -f "$PLAIN/.kontourai/flow-agents/liveness/events.jsonl" ]]; then
|
|
441
|
+
_pass "AC6: the claim landed at exactly <repo>/.kontourai/flow-agents/liveness (unchanged, single-checkout path)"
|
|
442
|
+
else
|
|
443
|
+
_fail "AC6: the claim did not land at the expected unchanged single-checkout path"
|
|
444
|
+
fi
|
|
445
|
+
|
|
446
|
+
LOCAL_ARTIFACT_ROOT_SUBDIR="lib"
|
|
447
|
+
LOCAL_ARTIFACT_ROOT_MODULE="$ROOT/build/src/$LOCAL_ARTIFACT_ROOT_SUBDIR/local-artifact-root.js"
|
|
448
|
+
RESOLVED_PLAIN_ROOT="$(node -e '
|
|
449
|
+
const { flowAgentsArtifactRoot } = require(process.argv[1]);
|
|
450
|
+
console.log(flowAgentsArtifactRoot(process.argv[2]));
|
|
451
|
+
' "$LOCAL_ARTIFACT_ROOT_MODULE" "$PLAIN")"
|
|
452
|
+
EXPECTED_PLAIN_ROOT="$(node -e 'console.log(require("path").resolve(process.argv[1], ".kontourai/flow-agents"))' "$PLAIN")"
|
|
453
|
+
if [[ "$RESOLVED_PLAIN_ROOT" == "$EXPECTED_PLAIN_ROOT" ]]; then
|
|
454
|
+
_pass "AC6: flowAgentsArtifactRoot(cwd) for a plain single-checkout repo is byte-identical to plain path.resolve(cwd, '.kontourai/flow-agents')"
|
|
455
|
+
else
|
|
456
|
+
_fail "AC6: flowAgentsArtifactRoot(cwd) diverged from the plain cwd-based path in the single-checkout case: got $RESOLVED_PLAIN_ROOT, expected $EXPECTED_PLAIN_ROOT"
|
|
457
|
+
fi
|
|
458
|
+
|
|
459
|
+
# Companion: a cwd with NO git repo at all (git resolution fails) must ALSO fall back to the
|
|
460
|
+
# exact plain cwd-based path -- the fail-open guarantee.
|
|
461
|
+
NONGIT="$TMP/non-git-dir"
|
|
462
|
+
mkdir -p "$NONGIT"
|
|
463
|
+
RESOLVED_NONGIT_ROOT="$(node -e '
|
|
464
|
+
const { flowAgentsArtifactRoot } = require(process.argv[1]);
|
|
465
|
+
console.log(flowAgentsArtifactRoot(process.argv[2]));
|
|
466
|
+
' "$LOCAL_ARTIFACT_ROOT_MODULE" "$NONGIT")"
|
|
467
|
+
EXPECTED_NONGIT_ROOT="$(node -e 'console.log(require("path").resolve(process.argv[1], ".kontourai/flow-agents"))' "$NONGIT")"
|
|
468
|
+
if [[ "$RESOLVED_NONGIT_ROOT" == "$EXPECTED_NONGIT_ROOT" ]]; then
|
|
469
|
+
_pass "AC6: flowAgentsArtifactRoot(cwd) for a non-git directory fails open to the plain cwd-based path, byte-identical to today"
|
|
470
|
+
else
|
|
471
|
+
_fail "AC6: flowAgentsArtifactRoot(cwd) for a non-git directory did not fail open correctly: got $RESOLVED_NONGIT_ROOT, expected $EXPECTED_NONGIT_ROOT"
|
|
472
|
+
fi
|
|
473
|
+
|
|
474
|
+
# ==== AC6b: subdirectory cwd resolves to the SHARED REPO-ROOT store (#413 iteration-2 Fix 2) ====
|
|
475
|
+
# Intentional, NOT byte-identical: `git rev-parse --git-common-dir` from a subdirectory returns a
|
|
476
|
+
# RELATIVE path this resolver walks up to the repo root, so a subdirectory invocation now
|
|
477
|
+
# correctly resolves to <repo-root>/.kontourai/flow-agents -- the SAME shared store every other
|
|
478
|
+
# cwd in this repo resolves to -- rather than the OLD cwd-anchored path.resolve(cwd, ...) a
|
|
479
|
+
# subdirectory invocation used to produce. See this eval's header docstring (AC6b) for the full
|
|
480
|
+
# rationale and the Fix 2a grep confirming no real call site depends on the old subdir-anchored
|
|
481
|
+
# behavior.
|
|
482
|
+
echo ""
|
|
483
|
+
echo "=== AC6b: subdirectory cwd resolves to the shared repo-root store (intended, not byte-identical) ==="
|
|
484
|
+
|
|
485
|
+
SUBDIR="$PLAIN/nested/deeper"
|
|
486
|
+
mkdir -p "$SUBDIR"
|
|
487
|
+
RESOLVED_SUBDIR_ROOT="$(node -e '
|
|
488
|
+
const { flowAgentsArtifactRoot } = require(process.argv[1]);
|
|
489
|
+
console.log(flowAgentsArtifactRoot(process.argv[2]));
|
|
490
|
+
' "$LOCAL_ARTIFACT_ROOT_MODULE" "$SUBDIR")"
|
|
491
|
+
EXPECTED_SUBDIR_ROOT="$(node -e 'console.log(require("path").resolve(process.argv[1], ".kontourai/flow-agents"))' "$PLAIN")"
|
|
492
|
+
OLD_CWD_ANCHORED_ROOT="$(node -e 'console.log(require("path").resolve(process.argv[1], ".kontourai/flow-agents"))' "$SUBDIR")"
|
|
493
|
+
|
|
494
|
+
if [[ "$RESOLVED_SUBDIR_ROOT" == "$EXPECTED_SUBDIR_ROOT" ]]; then
|
|
495
|
+
_pass "AC6b: flowAgentsArtifactRoot(subdirCwd) resolves to the REPO-ROOT shared store <repo>/.kontourai/flow-agents, not the subdir"
|
|
496
|
+
else
|
|
497
|
+
_fail "AC6b: flowAgentsArtifactRoot(subdirCwd) did not resolve to the repo-root store: got $RESOLVED_SUBDIR_ROOT, expected $EXPECTED_SUBDIR_ROOT"
|
|
498
|
+
fi
|
|
499
|
+
if [[ "$RESOLVED_SUBDIR_ROOT" != "$OLD_CWD_ANCHORED_ROOT" ]]; then
|
|
500
|
+
_pass "AC6b: this genuinely differs from the OLD cwd-anchored subdir path (confirms this is the intended git-common-dir walk-up, not an accidental no-op)"
|
|
501
|
+
else
|
|
502
|
+
_fail "AC6b: resolved root unexpectedly matched the old cwd-anchored subdir path -- the git-common-dir walk-up did not take effect for this cwd"
|
|
503
|
+
fi
|
|
504
|
+
|
|
505
|
+
# ==== FIX 3 (#413 iteration-2, MEDIUM security): ambient GIT_DIR/GIT_COMMON_DIR must NOT redirect
|
|
506
|
+
# resolution to a DIFFERENT real repo =========================================================
|
|
507
|
+
# A SECOND, wholly unrelated real git repo, plus a VALID (not merely nonexistent, unlike Fix 1b's
|
|
508
|
+
# corruption fixture) ambient GIT_DIR pointing at that other repo's .git directory, invoked from
|
|
509
|
+
# inside $PLAIN (the AC6 single-checkout fixture). Without Fix 3's env-stripping, git would honor
|
|
510
|
+
# the ambient GIT_DIR and --git-common-dir would resolve to the OTHER repo, silently redirecting
|
|
511
|
+
# the shared .kontourai/flow-agents store there. With Fix 3, resolution must depend on cwd alone.
|
|
512
|
+
echo ""
|
|
513
|
+
echo "=== FIX 3: an ambient GIT_DIR pointing at a DIFFERENT real repo does not redirect the resolved store ==="
|
|
514
|
+
|
|
515
|
+
OTHER_REPO="$TMP/fix3-other-repo"
|
|
516
|
+
mkdir -p "$OTHER_REPO/kits"
|
|
517
|
+
(cd "$OTHER_REPO" && git init -q && git config user.email "test@test.local" && git config user.name "test" \
|
|
518
|
+
&& printf '# marker\n' > kits/.keep && git add -A && git commit -q -m "init other repo")
|
|
519
|
+
if [[ -d "$OTHER_REPO/.git" ]]; then
|
|
520
|
+
_pass "FIX 3 setup: a second, unrelated real git repo was created for the GIT_DIR redirection fixture"
|
|
521
|
+
else
|
|
522
|
+
_fail "FIX 3 setup: 'git init' for the other-repo fixture failed"
|
|
523
|
+
fi
|
|
524
|
+
|
|
525
|
+
FIX3_SUBJECT="fix3-git-dir-redirect-subject"
|
|
526
|
+
if (cd "$PLAIN" && GIT_DIR="$OTHER_REPO/.git" flow_agents_node "$WRITER" liveness claim "$FIX3_SUBJECT" --actor fix3-actor \
|
|
527
|
+
>"$TMP/fix3-claim.out" 2>"$TMP/fix3-claim.err"); then
|
|
528
|
+
_pass "FIX 3: liveness claim with an ambient GIT_DIR pointing at another repo exits 0"
|
|
529
|
+
else
|
|
530
|
+
_fail "FIX 3: liveness claim with an ambient GIT_DIR pointing at another repo unexpectedly failed: $(cat "$TMP/fix3-claim.out" "$TMP/fix3-claim.err")"
|
|
531
|
+
fi
|
|
532
|
+
|
|
533
|
+
if [[ -f "$PLAIN/.kontourai/flow-agents/liveness/events.jsonl" ]] && grep -qF "fix3-actor" "$PLAIN/.kontourai/flow-agents/liveness/events.jsonl"; then
|
|
534
|
+
_pass "FIX 3: the claim landed in PLAIN's OWN shared store (cwd-anchored), not redirected by the ambient GIT_DIR"
|
|
535
|
+
else
|
|
536
|
+
_fail "FIX 3: the claim did not land in PLAIN's own store -- ambient GIT_DIR may have redirected resolution"
|
|
537
|
+
fi
|
|
538
|
+
if [[ ! -d "$OTHER_REPO/.kontourai" ]]; then
|
|
539
|
+
_pass "FIX 3: the OTHER repo (named by the ambient GIT_DIR) received NO .kontourai artifact at all -- resolution was not redirected there"
|
|
540
|
+
else
|
|
541
|
+
_fail "FIX 3: the OTHER repo unexpectedly received a .kontourai artifact -- ambient GIT_DIR redirected resolution (Fix 3 regressed)"
|
|
542
|
+
fi
|
|
543
|
+
|
|
544
|
+
# Direct resolver-level assertion (no subprocess env-inheritance ambiguity): resolveSharedRepoRoot
|
|
545
|
+
# invoked with an explicit ambient GIT_DIR pointing at OTHER_REPO must still resolve PLAIN's own
|
|
546
|
+
# root, never OTHER_REPO's.
|
|
547
|
+
RESOLVED_WITH_BAD_GIT_DIR="$(cd "$PLAIN" && GIT_DIR="$OTHER_REPO/.git" node -e '
|
|
548
|
+
const { resolveSharedRepoRoot } = require(process.argv[1]);
|
|
549
|
+
console.log(resolveSharedRepoRoot(process.argv[2]));
|
|
550
|
+
' "$LOCAL_ARTIFACT_ROOT_MODULE" "$PLAIN")"
|
|
551
|
+
REAL_PLAIN="$(cd "$PLAIN" && pwd -P)"
|
|
552
|
+
REAL_OTHER_REPO="$(cd "$OTHER_REPO" && pwd -P)"
|
|
553
|
+
if [[ -n "$RESOLVED_WITH_BAD_GIT_DIR" ]]; then
|
|
554
|
+
REAL_RESOLVED_WITH_BAD_GIT_DIR="$(cd "$RESOLVED_WITH_BAD_GIT_DIR" && pwd -P)"
|
|
555
|
+
if [[ "$REAL_RESOLVED_WITH_BAD_GIT_DIR" == "$REAL_PLAIN" ]]; then
|
|
556
|
+
_pass "FIX 3: resolveSharedRepoRoot(PLAIN cwd) with ambient GIT_DIR=<other repo> still resolves PLAIN's own root"
|
|
557
|
+
elif [[ "$REAL_RESOLVED_WITH_BAD_GIT_DIR" == "$REAL_OTHER_REPO" ]]; then
|
|
558
|
+
_fail "FIX 3: resolveSharedRepoRoot was REDIRECTED to the other repo named by ambient GIT_DIR -- Fix 3 regressed"
|
|
559
|
+
else
|
|
560
|
+
_fail "FIX 3: resolveSharedRepoRoot resolved to neither expected root: got $REAL_RESOLVED_WITH_BAD_GIT_DIR"
|
|
561
|
+
fi
|
|
562
|
+
else
|
|
563
|
+
_fail "FIX 3: resolveSharedRepoRoot(PLAIN cwd) with ambient GIT_DIR returned nothing"
|
|
564
|
+
fi
|
|
565
|
+
|
|
566
|
+
# ---- Summary ----
|
|
567
|
+
echo ""
|
|
568
|
+
echo "----------------------------------------------"
|
|
569
|
+
if [[ $errors -eq 0 ]]; then
|
|
570
|
+
echo "test_liveness_worktree_root: all checks passed."
|
|
571
|
+
exit 0
|
|
572
|
+
else
|
|
573
|
+
echo "test_liveness_worktree_root: $errors check(s) failed."
|
|
574
|
+
exit 1
|
|
575
|
+
fi
|
|
@@ -201,13 +201,18 @@ flow_agents_node "workflow-sidecar" ensure-session \
|
|
|
201
201
|
--title "Composed Gate Claim Test" \
|
|
202
202
|
--summary "Test composed Builder publish/learn flow producer." \
|
|
203
203
|
--flow-id builder.build \
|
|
204
|
-
--step-id pr-open \
|
|
205
204
|
--timestamp "2026-06-26T00:00:00Z" >/dev/null 2>&1
|
|
206
205
|
|
|
207
206
|
flow_agents_node "workflow-sidecar" init-plan "$COMPOSED_ROOT/composed-gate-claim/composed-gate-claim--deliver.md" \
|
|
208
207
|
--source-request "Test" --summary "Testing" \
|
|
209
208
|
--timestamp "2026-06-26T00:00:00Z" >/dev/null 2>&1
|
|
210
209
|
|
|
210
|
+
flow_agents_node "workflow-sidecar" advance-state "$COMPOSED_ROOT/composed-gate-claim" \
|
|
211
|
+
--status in_progress --phase release \
|
|
212
|
+
--summary "Testing composed pr-open producer." --next-action "Record claim." \
|
|
213
|
+
--flow-definition builder.build \
|
|
214
|
+
--timestamp "2026-06-26T00:00:30Z" >/dev/null 2>&1
|
|
215
|
+
|
|
211
216
|
if flow_agents_node "workflow-sidecar" record-gate-claim "$COMPOSED_ROOT/composed-gate-claim" \
|
|
212
217
|
--status pass \
|
|
213
218
|
--summary "PR opened with verification evidence." \
|