@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,241 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Proves first-step workflow entry and provider-neutral local work-item anchoring (#438).
|
|
3
|
+
|
|
4
|
+
set -uo pipefail
|
|
5
|
+
|
|
6
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
7
|
+
source "$ROOT/evals/lib/node.sh"
|
|
8
|
+
|
|
9
|
+
TMP="$(mktemp -d)"
|
|
10
|
+
errors=0
|
|
11
|
+
trap 'rm -rf "$TMP"' EXIT
|
|
12
|
+
|
|
13
|
+
pass() { printf ' PASS %s\n' "$1"; }
|
|
14
|
+
fail() { printf ' FAIL %s\n' "$1"; errors=$((errors + 1)); }
|
|
15
|
+
|
|
16
|
+
WRITER="workflow-sidecar"
|
|
17
|
+
|
|
18
|
+
echo "=== Builder workflow entry enforcement ==="
|
|
19
|
+
|
|
20
|
+
REFUSED_ROOT="$TMP/refused/.kontourai/flow-agents"
|
|
21
|
+
if flow_agents_node "$WRITER" ensure-session \
|
|
22
|
+
--artifact-root "$REFUSED_ROOT" \
|
|
23
|
+
--task-slug refused-plan \
|
|
24
|
+
--title "Refused plan entry" \
|
|
25
|
+
--summary "Must enter through the declared prefix." \
|
|
26
|
+
--flow-id builder.build \
|
|
27
|
+
--step-id plan >"$TMP/refused.out" 2>&1; then
|
|
28
|
+
fail "fresh later-step entry is rejected"
|
|
29
|
+
elif [[ ! -e "$REFUSED_ROOT" ]] && grep -q 'must start at first step "pull-work"' "$TMP/refused.out"; then
|
|
30
|
+
pass "fresh later-step entry is rejected before artifact-root creation"
|
|
31
|
+
else
|
|
32
|
+
fail "later-step refusal wrote files or returned the wrong diagnostic: $(cat "$TMP/refused.out")"
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
AD_HOC_ROOT="$TMP/ad-hoc/.kontourai/flow-agents"
|
|
36
|
+
if flow_agents_node "$WRITER" ensure-session \
|
|
37
|
+
--artifact-root "$AD_HOC_ROOT" \
|
|
38
|
+
--task-slug refused-ad-hoc \
|
|
39
|
+
--title "Refused ad-hoc entry" \
|
|
40
|
+
--summary "A reason is not workflow authority." \
|
|
41
|
+
--flow-id builder.build \
|
|
42
|
+
--step-id plan \
|
|
43
|
+
--ad-hoc-reason "skip the prefix" >"$TMP/ad-hoc.out" 2>&1; then
|
|
44
|
+
fail "ad-hoc reason cannot authorize later-step entry"
|
|
45
|
+
elif [[ ! -e "$AD_HOC_ROOT" ]] && grep -q 'cannot authorize workflow entry' "$TMP/ad-hoc.out"; then
|
|
46
|
+
pass "ad-hoc reason cannot authorize later-step entry or write artifacts"
|
|
47
|
+
else
|
|
48
|
+
fail "ad-hoc refusal wrote files or returned the wrong diagnostic: $(cat "$TMP/ad-hoc.out")"
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
LOCAL_ROOT="$TMP/local/.kontourai/flow-agents"
|
|
52
|
+
if flow_agents_node "$WRITER" ensure-session \
|
|
53
|
+
--artifact-root "$LOCAL_ROOT" \
|
|
54
|
+
--task-slug local-request \
|
|
55
|
+
--title "Local request" \
|
|
56
|
+
--summary "Providerless work still needs an anchor." \
|
|
57
|
+
--flow-id builder.build \
|
|
58
|
+
--timestamp "2026-07-10T00:00:00Z" >"$TMP/local.out" 2>&1; then
|
|
59
|
+
if node - "$LOCAL_ROOT" <<'NODE'
|
|
60
|
+
const fs = require('node:fs');
|
|
61
|
+
const path = require('node:path');
|
|
62
|
+
const root = process.argv[2];
|
|
63
|
+
const current = JSON.parse(fs.readFileSync(path.join(root, 'current.json'), 'utf8'));
|
|
64
|
+
const state = JSON.parse(fs.readFileSync(path.join(root, 'local-request', 'state.json'), 'utf8'));
|
|
65
|
+
const workItem = JSON.parse(fs.readFileSync(path.join(root, 'local-request', 'work-item.json'), 'utf8'));
|
|
66
|
+
if (current.active_flow_id !== 'builder.build' || current.active_step_id !== 'pull-work') process.exit(1);
|
|
67
|
+
if (state.status !== 'new' || state.phase !== 'pickup') process.exit(1);
|
|
68
|
+
if (JSON.stringify(state.work_item_refs) !== JSON.stringify(['local:local-request'])) process.exit(1);
|
|
69
|
+
if (workItem.id !== 'local-request' || workItem.title !== 'Local request') process.exit(1);
|
|
70
|
+
if (workItem.source_provider?.kind !== 'local' || workItem.source_provider?.path !== 'work-item.json') process.exit(1);
|
|
71
|
+
if (!state.next_action?.summary?.includes('builder-run start') || !state.next_action?.summary?.includes('`pull-work`')) process.exit(1);
|
|
72
|
+
NODE
|
|
73
|
+
then
|
|
74
|
+
pass "providerless request creates a local Work Item and starts at pull-work"
|
|
75
|
+
else
|
|
76
|
+
fail "local Work Item or first-step state is invalid"
|
|
77
|
+
fi
|
|
78
|
+
else
|
|
79
|
+
fail "providerless Builder entry failed: $(cat "$TMP/local.out")"
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
LOCAL_SESSION="$LOCAL_ROOT/local-request"
|
|
83
|
+
if flow_agents_node builder-run start --session-dir "$LOCAL_SESSION" >"$TMP/builder-start.out" 2>&1 \
|
|
84
|
+
&& node - "$TMP/local" "$LOCAL_SESSION" <<'NODE'
|
|
85
|
+
const fs = require('node:fs');
|
|
86
|
+
const path = require('node:path');
|
|
87
|
+
const project = process.argv[2];
|
|
88
|
+
const session = process.argv[3];
|
|
89
|
+
const flowState = JSON.parse(fs.readFileSync(path.join(project, '.kontourai', 'flow', 'runs', 'local-request', 'state.json'), 'utf8'));
|
|
90
|
+
const sidecar = JSON.parse(fs.readFileSync(path.join(session, 'state.json'), 'utf8'));
|
|
91
|
+
if (flowState.current_step !== 'pull-work' || flowState.subject !== 'local:local-request') process.exit(1);
|
|
92
|
+
if (sidecar.flow_run?.current_step !== 'pull-work') process.exit(1);
|
|
93
|
+
if (JSON.stringify(sidecar.next_action?.skills) !== JSON.stringify(['pull-work'])) process.exit(1);
|
|
94
|
+
if (!sidecar.next_action?.command?.includes('builder-run sync')) process.exit(1);
|
|
95
|
+
NODE
|
|
96
|
+
then
|
|
97
|
+
pass "small-model entry command creates canonical Flow run and projects pull-work action"
|
|
98
|
+
else
|
|
99
|
+
fail "canonical Builder run did not start or project correctly: $(cat "$TMP/builder-start.out")"
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
if flow_agents_node "$WRITER" record-gate-claim "$LOCAL_SESSION" \
|
|
103
|
+
--expectation selected-work \
|
|
104
|
+
--status pass \
|
|
105
|
+
--summary "Selected local:local-request with scope and acceptance context." \
|
|
106
|
+
--evidence-ref-json '{"kind":"artifact","file":".kontourai/flow-agents/local-request/work-item.json","summary":"Provider-neutral local Work Item."}' \
|
|
107
|
+
>"$TMP/selected-work.out" 2>&1 \
|
|
108
|
+
&& node - "$TMP/local" "$LOCAL_SESSION" <<'NODE'
|
|
109
|
+
const fs = require('node:fs');
|
|
110
|
+
const path = require('node:path');
|
|
111
|
+
const project = process.argv[2];
|
|
112
|
+
const session = process.argv[3];
|
|
113
|
+
const flowState = JSON.parse(fs.readFileSync(path.join(project, '.kontourai', 'flow', 'runs', 'local-request', 'state.json'), 'utf8'));
|
|
114
|
+
const sidecar = JSON.parse(fs.readFileSync(path.join(session, 'state.json'), 'utf8'));
|
|
115
|
+
if (flowState.current_step !== 'design-probe') process.exit(1);
|
|
116
|
+
if (sidecar.flow_run?.current_step !== 'design-probe') process.exit(1);
|
|
117
|
+
if (JSON.stringify(sidecar.next_action?.skills) !== JSON.stringify(['pickup-probe'])) process.exit(1);
|
|
118
|
+
NODE
|
|
119
|
+
then
|
|
120
|
+
pass "gate-claim write synchronizes Flow and projects the next skill"
|
|
121
|
+
else
|
|
122
|
+
fail "selected-work claim did not advance the canonical run: $(cat "$TMP/selected-work.out")"
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
if FLOW_AGENTS_GOAL_FIT_MODE=block FLOW_AGENTS_GOAL_FIT_MAX_BLOCKS=100000 \
|
|
126
|
+
node "$ROOT/scripts/hooks/stop-goal-fit.js" >"$TMP/stop.out" 2>"$TMP/stop.err" <<JSON
|
|
127
|
+
{"hook_event_name":"Stop","cwd":"$TMP/local"}
|
|
128
|
+
JSON
|
|
129
|
+
then
|
|
130
|
+
fail "active pre-execution Flow run should block Stop"
|
|
131
|
+
else
|
|
132
|
+
stop_status=$?
|
|
133
|
+
if [[ "$stop_status" -eq 2 ]] \
|
|
134
|
+
&& grep -q 'required skills: pickup-probe' "$TMP/stop.err" \
|
|
135
|
+
&& grep -q 'builder-run sync' "$TMP/stop.err" \
|
|
136
|
+
&& grep -q 'release skipped for active Flow run' "$TMP/stop.err" \
|
|
137
|
+
&& node - "$LOCAL_SESSION/state.json" <<'NODE'
|
|
138
|
+
const fs = require('node:fs');
|
|
139
|
+
const state = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
|
|
140
|
+
if (state.flow_run?.status !== 'active' || state.flow_run?.current_step !== 'design-probe') process.exit(1);
|
|
141
|
+
NODE
|
|
142
|
+
then
|
|
143
|
+
pass "active Flow run blocks Stop, preserves liveness, and exposes executable guidance"
|
|
144
|
+
else
|
|
145
|
+
fail "active Flow Stop enforcement or guidance was incomplete (exit $stop_status): $(cat "$TMP/stop.err")"
|
|
146
|
+
fi
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
if flow_agents_node "$WRITER" record-gate-claim "$LOCAL_SESSION" \
|
|
150
|
+
--expectation pickup-probe-readiness \
|
|
151
|
+
--status pass \
|
|
152
|
+
--summary "Pickup Probe confirmed scope and planning readiness." \
|
|
153
|
+
--evidence-ref-json '{"kind":"artifact","file":".kontourai/flow-agents/local-request/work-item.json","summary":"Probe fixture evidence."}' >/dev/null 2>&1 \
|
|
154
|
+
&& flow_agents_node "$WRITER" record-gate-claim "$LOCAL_SESSION" \
|
|
155
|
+
--expectation probe-decisions-or-accepted-gaps \
|
|
156
|
+
--status pass \
|
|
157
|
+
--summary "Probe decisions and accepted gaps are recorded." \
|
|
158
|
+
--evidence-ref-json '{"kind":"artifact","file":".kontourai/flow-agents/local-request/work-item.json","summary":"Probe decision fixture evidence."}' >/dev/null 2>&1 \
|
|
159
|
+
&& flow_agents_node "$WRITER" record-gate-claim "$LOCAL_SESSION" \
|
|
160
|
+
--expectation implementation-plan \
|
|
161
|
+
--status pass \
|
|
162
|
+
--summary "Implementation plan records files, sequence, and evidence." \
|
|
163
|
+
--evidence-ref-json '{"kind":"artifact","file":".kontourai/flow-agents/local-request/work-item.json","summary":"Plan fixture evidence."}' >/dev/null 2>&1 \
|
|
164
|
+
&& flow_agents_node "$WRITER" record-gate-claim "$LOCAL_SESSION" \
|
|
165
|
+
--expectation implementation-scope \
|
|
166
|
+
--status pass \
|
|
167
|
+
--summary "Implementation scope and changed files are recorded." \
|
|
168
|
+
--evidence-ref-json '{"kind":"artifact","file":".kontourai/flow-agents/local-request/work-item.json","summary":"Execution fixture evidence."}' >/dev/null 2>&1 \
|
|
169
|
+
&& flow_agents_node "$WRITER" record-gate-claim "$LOCAL_SESSION" \
|
|
170
|
+
--expectation tests-evidence \
|
|
171
|
+
--status fail \
|
|
172
|
+
--route-reason implementation_defect \
|
|
173
|
+
--summary "Verification found an implementation defect." \
|
|
174
|
+
--evidence-ref-json '{"kind":"artifact","file":".kontourai/flow-agents/local-request/work-item.json","summary":"Failed verification fixture evidence."}' >"$TMP/route-back.out" 2>&1 \
|
|
175
|
+
&& node - "$TMP/local" "$LOCAL_SESSION" <<'NODE'
|
|
176
|
+
const fs = require('node:fs');
|
|
177
|
+
const path = require('node:path');
|
|
178
|
+
const project = process.argv[2];
|
|
179
|
+
const session = process.argv[3];
|
|
180
|
+
const flowState = JSON.parse(fs.readFileSync(path.join(project, '.kontourai', 'flow', 'runs', 'local-request', 'state.json'), 'utf8'));
|
|
181
|
+
const sidecar = JSON.parse(fs.readFileSync(path.join(session, 'state.json'), 'utf8'));
|
|
182
|
+
if (flowState.current_step !== 'execute') process.exit(1);
|
|
183
|
+
if (sidecar.flow_run?.route_back_attempt !== 1 || sidecar.flow_run?.route_back_max_attempts !== 3) process.exit(1);
|
|
184
|
+
if (JSON.stringify(sidecar.next_action?.skills) !== JSON.stringify(['execute-plan'])) process.exit(1);
|
|
185
|
+
if (!sidecar.next_action?.summary?.includes('Route-back history: attempt 1/3')) process.exit(1);
|
|
186
|
+
NODE
|
|
187
|
+
then
|
|
188
|
+
pass "sidecar failure classifier drives Flow route-back and projects its attempt budget"
|
|
189
|
+
else
|
|
190
|
+
fail "route-back classifier did not produce the canonical execute retry: $(cat "$TMP/route-back.out" 2>/dev/null)"
|
|
191
|
+
fi
|
|
192
|
+
|
|
193
|
+
PROVIDER_ROOT="$TMP/provider/.kontourai/flow-agents"
|
|
194
|
+
if flow_agents_node "$WRITER" ensure-session \
|
|
195
|
+
--artifact-root "$PROVIDER_ROOT" \
|
|
196
|
+
--work-item "kontourai/flow-agents#438" \
|
|
197
|
+
--title "Provider work item" \
|
|
198
|
+
--summary "Keep the provider reference." \
|
|
199
|
+
--flow-id builder.build \
|
|
200
|
+
--timestamp "2026-07-10T00:01:00Z" >/dev/null 2>&1 \
|
|
201
|
+
&& node - "$PROVIDER_ROOT/kontourai-flow-agents-438/state.json" <<'NODE'
|
|
202
|
+
const fs = require('node:fs');
|
|
203
|
+
const state = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
|
|
204
|
+
if (JSON.stringify(state.work_item_refs) !== JSON.stringify(['kontourai/flow-agents#438'])) process.exit(1);
|
|
205
|
+
NODE
|
|
206
|
+
then
|
|
207
|
+
pass "provider-backed request preserves its neutral work-item ref"
|
|
208
|
+
else
|
|
209
|
+
fail "provider-backed work-item ref was not persisted"
|
|
210
|
+
fi
|
|
211
|
+
|
|
212
|
+
# A direct primitive remains usable without claiming Builder prefix completion.
|
|
213
|
+
STANDALONE_ROOT="$TMP/standalone/.kontourai/flow-agents"
|
|
214
|
+
if flow_agents_node "$WRITER" ensure-session \
|
|
215
|
+
--artifact-root "$STANDALONE_ROOT" \
|
|
216
|
+
--task-slug standalone-plan \
|
|
217
|
+
--title "Standalone plan" \
|
|
218
|
+
--summary "Direct primitive session." \
|
|
219
|
+
--timestamp "2026-07-10T00:02:00Z" >/dev/null 2>&1 \
|
|
220
|
+
&& node - "$STANDALONE_ROOT" <<'NODE'
|
|
221
|
+
const fs = require('node:fs');
|
|
222
|
+
const path = require('node:path');
|
|
223
|
+
const root = process.argv[2];
|
|
224
|
+
const current = JSON.parse(fs.readFileSync(path.join(root, 'current.json'), 'utf8'));
|
|
225
|
+
const state = JSON.parse(fs.readFileSync(path.join(root, 'standalone-plan', 'state.json'), 'utf8'));
|
|
226
|
+
if ('active_flow_id' in current || 'active_step_id' in current) process.exit(1);
|
|
227
|
+
if (state.status !== 'planned' || state.phase !== 'planning') process.exit(1);
|
|
228
|
+
if (JSON.stringify(state.work_item_refs) !== JSON.stringify(['local:standalone-plan'])) process.exit(1);
|
|
229
|
+
NODE
|
|
230
|
+
then
|
|
231
|
+
pass "standalone primitive session remains available without a Builder stamp"
|
|
232
|
+
else
|
|
233
|
+
fail "standalone primitive session was incorrectly stamped as Builder"
|
|
234
|
+
fi
|
|
235
|
+
|
|
236
|
+
if [[ "$errors" -gt 0 ]]; then
|
|
237
|
+
printf 'test_builder_entry_enforcement: %d failure(s)\n' "$errors" >&2
|
|
238
|
+
exit 1
|
|
239
|
+
fi
|
|
240
|
+
|
|
241
|
+
echo "test_builder_entry_enforcement: all checks passed"
|
|
@@ -41,21 +41,29 @@ trap cleanup EXIT
|
|
|
41
41
|
|
|
42
42
|
# ─── Helper: set active_step_id for a step.
|
|
43
43
|
# For steps in the phase_map, use advance-state.
|
|
44
|
-
# For design-probe (no phase mapping),
|
|
44
|
+
# For design-probe (no phase mapping), mutate only the test fixture's current pointer.
|
|
45
45
|
# ──────────────────────────────────────────────────────────────────
|
|
46
46
|
set_active_step() {
|
|
47
47
|
local aroot="$1" slug="$2" step="$3"
|
|
48
48
|
case "$step" in
|
|
49
49
|
design-probe)
|
|
50
|
-
#
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
50
|
+
# The production entry surface cannot select a later step. This producer-focused
|
|
51
|
+
# fixture sets the pointer directly because design-probe has no legacy phase_map key.
|
|
52
|
+
node - "$aroot" <<'NODE'
|
|
53
|
+
const fs = require('node:fs');
|
|
54
|
+
const path = require('node:path');
|
|
55
|
+
const root = process.argv[2];
|
|
56
|
+
const files = [path.join(root, 'current.json')];
|
|
57
|
+
const actorRoot = path.join(root, 'current');
|
|
58
|
+
if (fs.existsSync(actorRoot)) {
|
|
59
|
+
files.push(...fs.readdirSync(actorRoot).filter((name) => name.endsWith('.json')).map((name) => path.join(actorRoot, name)));
|
|
60
|
+
}
|
|
61
|
+
for (const file of files) {
|
|
62
|
+
const current = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
63
|
+
current.active_step_id = 'design-probe';
|
|
64
|
+
fs.writeFileSync(file, `${JSON.stringify(current, null, 2)}\n`);
|
|
65
|
+
}
|
|
66
|
+
NODE
|
|
59
67
|
;;
|
|
60
68
|
pull-work)
|
|
61
69
|
flow_agents_node "workflow-sidecar" advance-state "$aroot/$slug" \
|
|
@@ -86,6 +86,20 @@ else
|
|
|
86
86
|
_fail "Codex install with Kontour hosted Console telemetry config failed"
|
|
87
87
|
fi
|
|
88
88
|
|
|
89
|
+
CODEX_HOSTED_CONSOLE_NO_TENANT_DEST="$TMPDIR_EVAL/codex-hosted-console-no-tenant-workspace"
|
|
90
|
+
CODEX_HOSTED_CONSOLE_NO_TENANT_STDERR="$TMPDIR_EVAL/codex-hosted-console-no-tenant-stderr.txt"
|
|
91
|
+
if (cd "$ROOT_DIR/dist/codex" && bash install.sh "$CODEX_HOSTED_CONSOLE_NO_TENANT_DEST" --telemetry-sink kontour-hosted-console --console-token-file "$CONSOLE_TOKEN_FILE" >/dev/null 2>"$CODEX_HOSTED_CONSOLE_NO_TENANT_STDERR"); then
|
|
92
|
+
_pass "Codex install with hosted Console sink and no --console-tenant still succeeds"
|
|
93
|
+
else
|
|
94
|
+
_fail "Codex install with hosted Console sink and no --console-tenant unexpectedly failed"
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
if rg -q 'warning: install-console-config.sh: a Console telemetry sink was selected with no --console-tenant' "$CODEX_HOSTED_CONSOLE_NO_TENANT_STDERR"; then
|
|
98
|
+
_pass "Untenanted hosted Console sink install prints DX warning to stderr"
|
|
99
|
+
else
|
|
100
|
+
_fail "Untenanted hosted Console sink install did not print DX warning to stderr"
|
|
101
|
+
fi
|
|
102
|
+
|
|
89
103
|
if (cd "$ROOT_DIR/dist/codex" && bash install.sh "$CODEX_USER_HOSTED_CONSOLE_DEST" --telemetry-sink user-hosted-console --console-url https://console.example.test --console-token-file "$CONSOLE_TOKEN_FILE" >/dev/null); then
|
|
90
104
|
_pass "Codex install with user-hosted Console telemetry config succeeded"
|
|
91
105
|
else
|
|
@@ -116,6 +130,137 @@ else
|
|
|
116
130
|
_fail "flow-agents init headless Codex install failed"
|
|
117
131
|
fi
|
|
118
132
|
|
|
133
|
+
echo ""
|
|
134
|
+
echo "--- Guided Console-Connect Wizard (G2/G3): headless regression + summary/verify ---"
|
|
135
|
+
|
|
136
|
+
# New (G3): a plain local-files headless install still prints the structured
|
|
137
|
+
# post-install summary block (regardless of the guided wizard, which only
|
|
138
|
+
# fires for interactive installs) -- the summary/verify tail is shared by
|
|
139
|
+
# both install paths.
|
|
140
|
+
BASE_SUMMARY_LOCAL_DEST="$TMPDIR_EVAL/base-summary-local-workspace"
|
|
141
|
+
BASE_SUMMARY_LOCAL_STDOUT="$TMPDIR_EVAL/base-summary-local-stdout.txt"
|
|
142
|
+
if node "$ROOT_DIR/build/src/cli.js" init --dest "$BASE_SUMMARY_LOCAL_DEST" --telemetry-sink local-files --yes >"$BASE_SUMMARY_LOCAL_STDOUT" 2>&1; then
|
|
143
|
+
_pass "flow-agents init headless local-files install succeeded (G3 summary case)"
|
|
144
|
+
else
|
|
145
|
+
_fail "flow-agents init headless local-files install failed (G3 summary case)"
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
if rg -q 'Console:' "$BASE_SUMMARY_LOCAL_STDOUT" && rg -q 'local-only' "$BASE_SUMMARY_LOCAL_STDOUT" && rg -F -q "$BASE_SUMMARY_LOCAL_DEST" "$BASE_SUMMARY_LOCAL_STDOUT"; then
|
|
149
|
+
_pass "flow-agents init prints G3 post-install summary block for a local-files headless install"
|
|
150
|
+
else
|
|
151
|
+
_fail "flow-agents init did not print G3 post-install summary block for a local-files headless install"
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
# New (G2/G3): a kontour-hosted-console headless install auto-verifies via
|
|
155
|
+
# telemetry-doctor's buildReport in-process, stays within a bounded wall-clock
|
|
156
|
+
# time (never hangs), never prints the raw console token, and prints a
|
|
157
|
+
# console status line -- verified or unverified is acceptable in a sandboxed
|
|
158
|
+
# CI network, as long as the check happened honestly.
|
|
159
|
+
BASE_SUMMARY_HOSTED_DEST="$TMPDIR_EVAL/base-summary-hosted-workspace"
|
|
160
|
+
BASE_SUMMARY_HOSTED_STDOUT="$TMPDIR_EVAL/base-summary-hosted-stdout.txt"
|
|
161
|
+
SECONDS=0
|
|
162
|
+
if node "$ROOT_DIR/build/src/cli.js" init --dest "$BASE_SUMMARY_HOSTED_DEST" --telemetry-sink kontour-hosted-console --console-tenant tenant-x --console-token-file "$CONSOLE_TOKEN_FILE" --yes >"$BASE_SUMMARY_HOSTED_STDOUT" 2>&1; then
|
|
163
|
+
_pass "flow-agents init headless kontour-hosted-console install succeeded (G2/G3 case)"
|
|
164
|
+
else
|
|
165
|
+
_fail "flow-agents init headless kontour-hosted-console install failed (G2/G3 case)"
|
|
166
|
+
fi
|
|
167
|
+
HOSTED_SUMMARY_ELAPSED_SECONDS=$SECONDS
|
|
168
|
+
|
|
169
|
+
if [[ "$HOSTED_SUMMARY_ELAPSED_SECONDS" -le 20 ]]; then
|
|
170
|
+
_pass "flow-agents init auto-verify (G2) completed within a bounded wall-clock time (${HOSTED_SUMMARY_ELAPSED_SECONDS}s, never hangs)"
|
|
171
|
+
else
|
|
172
|
+
_fail "flow-agents init auto-verify (G2) took too long (${HOSTED_SUMMARY_ELAPSED_SECONDS}s) -- may be hanging"
|
|
173
|
+
fi
|
|
174
|
+
|
|
175
|
+
if rg -q 'Console:' "$BASE_SUMMARY_HOSTED_STDOUT" && ! rg -F -q 'test-token' "$BASE_SUMMARY_HOSTED_STDOUT"; then
|
|
176
|
+
_pass "flow-agents init prints a G2/G3 console status line and never prints the raw console token"
|
|
177
|
+
else
|
|
178
|
+
_fail "flow-agents init did not print a console status line, or leaked the raw console token"
|
|
179
|
+
fi
|
|
180
|
+
|
|
181
|
+
# New (regression, RESOLVED OWNER DECISION): the self-hosted blank-URL guard
|
|
182
|
+
# (fallback to local-files with a warning) applies to the INTERACTIVE wizard
|
|
183
|
+
# ONLY. The headless (--yes/--headless) path keeps today's unchanged
|
|
184
|
+
# behavior -- install-console-config.sh's existing die-on-blank for
|
|
185
|
+
# user-hosted-console with no --console-url/--console-endpoint. This asserts
|
|
186
|
+
# the exact same non-zero exit + die message current main() (pre-PR2)
|
|
187
|
+
# already produces, proving headless is byte-for-byte untouched. (The
|
|
188
|
+
# interactive fallback itself is proven by the console-connect-options.test.mjs
|
|
189
|
+
# unit tests on resolveSelfHostedUrlOrFallback + runConsoleConnectWizard.)
|
|
190
|
+
BASE_SELF_HOSTED_NO_URL_DEST="$TMPDIR_EVAL/base-self-hosted-no-url-workspace"
|
|
191
|
+
BASE_SELF_HOSTED_NO_URL_STDERR="$TMPDIR_EVAL/base-self-hosted-no-url-stderr.txt"
|
|
192
|
+
set +e
|
|
193
|
+
node "$ROOT_DIR/build/src/cli.js" init --dest "$BASE_SELF_HOSTED_NO_URL_DEST" --telemetry-sink user-hosted-console --yes >/dev/null 2>"$BASE_SELF_HOSTED_NO_URL_STDERR"
|
|
194
|
+
BASE_SELF_HOSTED_NO_URL_EXIT=$?
|
|
195
|
+
set -e
|
|
196
|
+
|
|
197
|
+
if [[ "$BASE_SELF_HOSTED_NO_URL_EXIT" -ne 0 ]] && rg -q 'install-console-config.sh: user-hosted-console requires --console-url or --console-endpoint' "$BASE_SELF_HOSTED_NO_URL_STDERR"; then
|
|
198
|
+
_pass "flow-agents init headless user-hosted-console with no --console-url still dies exactly as before (self-hosted guard is interactive-only)"
|
|
199
|
+
else
|
|
200
|
+
_fail "flow-agents init headless user-hosted-console with no --console-url behavior changed (expected unchanged die-on-blank)"
|
|
201
|
+
fi
|
|
202
|
+
|
|
203
|
+
# New (review fix FIX 1): a self-hosted/BYO Console (non-local https, not the
|
|
204
|
+
# known hosted host) headless install stays honest -- connected-unverified,
|
|
205
|
+
# exit 0, reachability.checked stays false because --allow-network was not
|
|
206
|
+
# passed -- but the summary now includes an actionable next-step hint instead
|
|
207
|
+
# of leaving the operator with a bare "not checked". Never for local-only.
|
|
208
|
+
BASE_SUMMARY_SELFHOSTED_DEST="$TMPDIR_EVAL/base-summary-selfhosted-workspace"
|
|
209
|
+
BASE_SUMMARY_SELFHOSTED_STDOUT="$TMPDIR_EVAL/base-summary-selfhosted-stdout.txt"
|
|
210
|
+
if node "$ROOT_DIR/build/src/cli.js" init --dest "$BASE_SUMMARY_SELFHOSTED_DEST" --telemetry-sink user-hosted-console --console-url https://console.example.test --console-tenant tenant-selfhosted --console-token-file "$CONSOLE_TOKEN_FILE" --yes >"$BASE_SUMMARY_SELFHOSTED_STDOUT" 2>&1; then
|
|
211
|
+
_pass "flow-agents init headless self-hosted/BYO Console install succeeds (unverified is honest, not a failure)"
|
|
212
|
+
else
|
|
213
|
+
_fail "flow-agents init headless self-hosted/BYO Console install unexpectedly failed"
|
|
214
|
+
fi
|
|
215
|
+
|
|
216
|
+
if rg -q 'connected, unverified' "$BASE_SUMMARY_SELFHOSTED_STDOUT" \
|
|
217
|
+
&& rg -F -q 'flow-agents telemetry-doctor --allow-network' "$BASE_SUMMARY_SELFHOSTED_STDOUT" \
|
|
218
|
+
&& ! rg -F -q 'test-token' "$BASE_SUMMARY_SELFHOSTED_STDOUT"; then
|
|
219
|
+
_pass "flow-agents init summary discloses WHY self-hosted reachability is unverified with an actionable --allow-network hint, and never prints the raw token"
|
|
220
|
+
else
|
|
221
|
+
_fail "flow-agents init summary did not disclose the self-hosted not-checked reason with the --allow-network hint"
|
|
222
|
+
fi
|
|
223
|
+
|
|
224
|
+
if rg -F -q 'flow-agents telemetry-doctor --allow-network' "$BASE_SUMMARY_LOCAL_STDOUT"; then
|
|
225
|
+
_fail "flow-agents init printed the self-hosted --allow-network hint for a local-files-only install (should never appear there)"
|
|
226
|
+
else
|
|
227
|
+
_pass "flow-agents init does not print the self-hosted --allow-network hint for a local-files-only install"
|
|
228
|
+
fi
|
|
229
|
+
|
|
230
|
+
# New (review fix FIX 4, security): pin the validate-before-persist ordering
|
|
231
|
+
# invariant. The TS-side wizard validators (console-telemetry-validate.ts) are
|
|
232
|
+
# soft (validate-and-warn, never block); the ONLY reason a malformed value can
|
|
233
|
+
# never land in a persisted telemetry.conf is that install-console-config.sh's
|
|
234
|
+
# bash validators (validate_tenant et al.) run BEFORE any config key is
|
|
235
|
+
# written and `die` (non-zero exit) on failure. A tenant value containing a
|
|
236
|
+
# semicolon fails validate_tenant's charset (^[A-Za-z0-9._:-]+$ --
|
|
237
|
+
# scripts/telemetry/install-console-config.sh); assert the headless install
|
|
238
|
+
# dies non-zero via that exact bash validator, and that the hostile value
|
|
239
|
+
# never appears in the persisted telemetry.conf (which already exists at this
|
|
240
|
+
# point from the rsync'd template, since install-console-config.sh only
|
|
241
|
+
# validates -- and writes nothing -- before dying).
|
|
242
|
+
BASE_HOSTILE_TENANT_DEST="$TMPDIR_EVAL/base-hostile-tenant-workspace"
|
|
243
|
+
BASE_HOSTILE_TENANT_STDERR="$TMPDIR_EVAL/base-hostile-tenant-stderr.txt"
|
|
244
|
+
HOSTILE_TENANT='tenant;rm-rf-evidence'
|
|
245
|
+
set +e
|
|
246
|
+
node "$ROOT_DIR/build/src/cli.js" init --dest "$BASE_HOSTILE_TENANT_DEST" --telemetry-sink user-hosted-console --console-url https://console.example.test --console-tenant "$HOSTILE_TENANT" --yes >/dev/null 2>"$BASE_HOSTILE_TENANT_STDERR"
|
|
247
|
+
BASE_HOSTILE_TENANT_EXIT=$?
|
|
248
|
+
set -e
|
|
249
|
+
|
|
250
|
+
if [[ "$BASE_HOSTILE_TENANT_EXIT" -ne 0 ]] \
|
|
251
|
+
&& rg -q 'install-console-config.sh: --console-tenant contains unsupported characters' "$BASE_HOSTILE_TENANT_STDERR"; then
|
|
252
|
+
_pass "flow-agents init headless install with an illegal-character Console tenant dies via the bash validate_tenant gate (non-zero exit)"
|
|
253
|
+
else
|
|
254
|
+
_fail "flow-agents init headless install with an illegal-character Console tenant did not die via bash validate_tenant as expected"
|
|
255
|
+
fi
|
|
256
|
+
|
|
257
|
+
if [[ ! -f "$BASE_HOSTILE_TENANT_DEST/scripts/telemetry/telemetry.conf" ]] \
|
|
258
|
+
|| ! rg -F -q "$HOSTILE_TENANT" "$BASE_HOSTILE_TENANT_DEST/scripts/telemetry/telemetry.conf"; then
|
|
259
|
+
_pass "hostile Console tenant value never lands in telemetry.conf (validate-before-persist ordering invariant holds)"
|
|
260
|
+
else
|
|
261
|
+
_fail "hostile Console tenant value was persisted into telemetry.conf despite failing bash validate_tenant"
|
|
262
|
+
fi
|
|
263
|
+
|
|
119
264
|
if (cd "$ROOT_DIR/dist/opencode" && bash install.sh "$OPENCODE_DEST" >/dev/null); then
|
|
120
265
|
_pass "opencode install succeeded"
|
|
121
266
|
else
|
|
@@ -708,6 +853,33 @@ else
|
|
|
708
853
|
_fail "Codex full install is missing base agents"
|
|
709
854
|
fi
|
|
710
855
|
|
|
856
|
+
if node - "$CODEX_AGENTS_DIR" <<'NODE'
|
|
857
|
+
const fs = require("node:fs");
|
|
858
|
+
const path = require("node:path");
|
|
859
|
+
function stringAssignment(text, key, name) {
|
|
860
|
+
const matches = [...text.matchAll(new RegExp(`^${key}\\s*=\\s*"([^"]*)"\\s*$`, "gm"))];
|
|
861
|
+
if (matches.length !== 1) throw new Error(`${name}: expected exactly one ${key} assignment, found ${matches.length}`);
|
|
862
|
+
return matches[0][1];
|
|
863
|
+
}
|
|
864
|
+
const expected = {
|
|
865
|
+
"tool-planner.toml": ["gpt-5.6-sol", "high"],
|
|
866
|
+
"tool-worker.toml": ["gpt-5.6-terra", "high"],
|
|
867
|
+
"tool-code-reviewer.toml": ["gpt-5.6-sol", "high"],
|
|
868
|
+
"tool-security-reviewer.toml": ["gpt-5.6-sol", "high"],
|
|
869
|
+
"tool-verifier.toml": ["gpt-5.6-sol", "high"],
|
|
870
|
+
};
|
|
871
|
+
for (const [name, [model, effort]] of Object.entries(expected)) {
|
|
872
|
+
const text = fs.readFileSync(path.join(process.argv[2], name), "utf8");
|
|
873
|
+
if (stringAssignment(text, "model", name) !== model) throw new Error(`${name}: expected model ${model}`);
|
|
874
|
+
if (stringAssignment(text, "model_reasoning_effort", name) !== effort) throw new Error(`${name}: expected reasoning ${effort}`);
|
|
875
|
+
}
|
|
876
|
+
NODE
|
|
877
|
+
then
|
|
878
|
+
_pass "Codex full install preserves Builder specialist 5.6 role routing"
|
|
879
|
+
else
|
|
880
|
+
_fail "Codex full install lost Builder specialist role routing"
|
|
881
|
+
fi
|
|
882
|
+
|
|
711
883
|
if [[ -d "$CODEX_FULL_DEST/.codex/skills/plan-work" && -d "$CODEX_FULL_DEST/.codex/skills/deliver" && -d "$CODEX_FULL_DEST/.codex/skills/agentic-engineering" ]]; then
|
|
712
884
|
_pass "Codex full install ships kit-skills and standalone skills together"
|
|
713
885
|
else
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# test_console_tenant_isolation.sh — multi-tenant install proof (console epic fa#410 item #5).
|
|
3
|
+
#
|
|
4
|
+
# Closes the gap that `test_telemetry.sh` §7 (fake-curl capture) and
|
|
5
|
+
# `test_liveness_console_relay.sh` (real HTTP stub) leave open: both already prove
|
|
6
|
+
# single-tenant header injection, but neither proves that TWO DISTINCT tenants, each installed
|
|
7
|
+
# through the REAL config-writer path (`install-console-config.sh` -> `telemetry.conf` ->
|
|
8
|
+
# `config.sh` -> `transport.sh`), get correctly separated `x-console-tenant-id` /
|
|
9
|
+
# `Authorization: Bearer` headers on the wire with no cross-tenant leakage.
|
|
10
|
+
#
|
|
11
|
+
# IMPORTANT SCOPE BOUNDARY: this eval proves the CLIENT-SIDE WIRE CONTRACT only — that a
|
|
12
|
+
# correctly configured tenant-a install and a correctly configured tenant-b install each send
|
|
13
|
+
# their own (and never each other's) tenant/token headers to the network layer. It does NOT
|
|
14
|
+
# prove server-side Console tenant enforcement (that a token cannot be replayed against another
|
|
15
|
+
# tenant's data, that a misconfigured/malicious client can't spoof a tenant header, etc.) — that
|
|
16
|
+
# is Console-repo scope (console #98-#100 tenant hardening). Do not read a green run here as
|
|
17
|
+
# proof of end-to-end multi-tenant security.
|
|
18
|
+
#
|
|
19
|
+
# Deterministic (a local stub HTTP server, no network/model spend, self-cleaning). The Console
|
|
20
|
+
# POST fires in a detached/backgrounded subshell (transport.sh's console_post_json), so the
|
|
21
|
+
# stub-received assertions poll for the captured file rather than assuming synchronous delivery
|
|
22
|
+
# (mirrors test_liveness_console_relay.sh's wait_for_post).
|
|
23
|
+
# Usage: bash evals/integration/test_console_tenant_isolation.sh
|
|
24
|
+
|
|
25
|
+
set -uo pipefail
|
|
26
|
+
|
|
27
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
28
|
+
source "$ROOT/evals/lib/node.sh"
|
|
29
|
+
|
|
30
|
+
TMPDIR_EVAL="$(mktemp -d)"
|
|
31
|
+
trap 'rm -rf "$TMPDIR_EVAL"; [[ -n "${STUB_PID:-}" ]] && kill "$STUB_PID" 2>/dev/null' EXIT
|
|
32
|
+
PORT=38796
|
|
33
|
+
RECV="$TMPDIR_EVAL/recv.jsonl"
|
|
34
|
+
|
|
35
|
+
errors=0
|
|
36
|
+
pass() { echo " [PASS] $1"; }
|
|
37
|
+
fail() { echo " [FAIL] $1"; errors=$((errors + 1)); }
|
|
38
|
+
|
|
39
|
+
# Stub Console endpoint: append each received {method, headers, body} as one JSON line, shared
|
|
40
|
+
# across both tenant phases so the isolation assertions can positively confirm no cross-tenant
|
|
41
|
+
# header leaked into the OTHER tenant's captured request (not just that each phase's own header
|
|
42
|
+
# is correct in isolation).
|
|
43
|
+
start_stub() {
|
|
44
|
+
node -e '
|
|
45
|
+
const http=require("http"),fs=require("fs");
|
|
46
|
+
const out=process.argv[1];
|
|
47
|
+
const s=http.createServer((req,res)=>{let b="";req.on("data",d=>b+=d);req.on("end",()=>{
|
|
48
|
+
fs.appendFileSync(out, JSON.stringify({method:req.method,headers:req.headers,body:b})+"\n");
|
|
49
|
+
res.writeHead(200); res.end("ok");
|
|
50
|
+
});});
|
|
51
|
+
s.listen(Number(process.argv[2]),"127.0.0.1");
|
|
52
|
+
setTimeout(()=>process.exit(0),30000);
|
|
53
|
+
' "$RECV" "$PORT" >/dev/null 2>&1 &
|
|
54
|
+
STUB_PID=$!
|
|
55
|
+
# wait for bind
|
|
56
|
+
for _ in $(seq 1 20); do
|
|
57
|
+
if node -e 'const n=require("net");const s=n.connect(Number(process.argv[1]),"127.0.0.1");s.on("connect",()=>{s.end();process.exit(0)});s.on("error",()=>process.exit(1))' "$PORT" 2>/dev/null; then return 0; fi
|
|
58
|
+
sleep 0.2
|
|
59
|
+
done
|
|
60
|
+
return 1
|
|
61
|
+
}
|
|
62
|
+
stop_stub() { [[ -n "${STUB_PID:-}" ]] && kill "$STUB_PID" 2>/dev/null; STUB_PID=""; }
|
|
63
|
+
|
|
64
|
+
# Poll the stub's received file for at least N lines (the POST is detached/async).
|
|
65
|
+
wait_for_lines() {
|
|
66
|
+
local n="$1" lines
|
|
67
|
+
for _ in $(seq 1 25); do
|
|
68
|
+
lines="$(wc -l <"$RECV" 2>/dev/null | tr -d ' ')"
|
|
69
|
+
[[ -n "$lines" && "$lines" -ge "$n" ]] && return 0
|
|
70
|
+
sleep 0.2
|
|
71
|
+
done
|
|
72
|
+
return 1
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
ENDPOINT="http://127.0.0.1:${PORT}/records"
|
|
76
|
+
|
|
77
|
+
# Install a tenant's real telemetry.conf via the REAL config writer, then emit one synthetic
|
|
78
|
+
# event through the REAL runtime path (config.sh's conf-key-to-env mapping, exercised by
|
|
79
|
+
# sourcing it, followed by transport.sh's console_telemetry_emit) — not a re-implementation of
|
|
80
|
+
# either.
|
|
81
|
+
install_and_emit() {
|
|
82
|
+
local tenant="$1" token_value="$2"
|
|
83
|
+
local work="$TMPDIR_EVAL/$tenant"
|
|
84
|
+
mkdir -p "$work"
|
|
85
|
+
local conf="$work/telemetry.conf"
|
|
86
|
+
local token_file="$work/token"
|
|
87
|
+
printf '%s' "$token_value" >"$token_file"
|
|
88
|
+
|
|
89
|
+
bash "$ROOT/scripts/telemetry/install-console-config.sh" "$conf" \
|
|
90
|
+
--console-endpoint "$ENDPOINT" \
|
|
91
|
+
--console-token-file "$token_file" \
|
|
92
|
+
--console-tenant "$tenant" >/dev/null
|
|
93
|
+
|
|
94
|
+
(
|
|
95
|
+
export TELEMETRY_CONFIG_FILE="$conf"
|
|
96
|
+
export TELEMETRY_DATA_DIR="$work/data"
|
|
97
|
+
export TELEMETRY_SESSION_DIR="$work/data/sessions"
|
|
98
|
+
unset TELEMETRY_DIR
|
|
99
|
+
source "$ROOT/scripts/telemetry/lib/config.sh"
|
|
100
|
+
source "$ROOT/scripts/telemetry/lib/transport.sh"
|
|
101
|
+
console_telemetry_emit '{"type":"test","note":"tenant-isolation-eval"}'
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
# Assert the Nth captured request (1-indexed) carries this tenant's own headers and never the
|
|
106
|
+
# other tenant's tenant-id/token — the actual "no cross-tenant leakage" proof.
|
|
107
|
+
check_capture() {
|
|
108
|
+
local line_no="$1" label="$2" own_tenant="$3" own_token="$4" other_tenant="$5" other_token="$6"
|
|
109
|
+
node -e '
|
|
110
|
+
const fs = require("fs");
|
|
111
|
+
const [recvPath, lineNoStr, ownTenant, ownToken, otherTenant, otherToken] = process.argv.slice(1);
|
|
112
|
+
const lineNo = Number(lineNoStr);
|
|
113
|
+
const lines = fs.readFileSync(recvPath, "utf8").trim().split("\n");
|
|
114
|
+
if (lines.length < lineNo) { console.log("BAD missing-captured-request"); process.exit(0); }
|
|
115
|
+
const r = JSON.parse(lines[lineNo - 1]);
|
|
116
|
+
const h = r.headers || {};
|
|
117
|
+
const raw = JSON.stringify(r);
|
|
118
|
+
const checks = [
|
|
119
|
+
["method-post", r.method === "POST"],
|
|
120
|
+
["own-tenant-header", h["x-console-tenant-id"] === ownTenant],
|
|
121
|
+
["own-auth-header", h["authorization"] === ("Bearer " + ownToken)],
|
|
122
|
+
["no-other-tenant-id-in-header", h["x-console-tenant-id"] !== otherTenant],
|
|
123
|
+
["no-other-token-in-header", h["authorization"] !== ("Bearer " + otherToken)],
|
|
124
|
+
["no-other-tenant-id-anywhere", !raw.includes(otherTenant)],
|
|
125
|
+
["no-other-token-anywhere", !raw.includes(otherToken)],
|
|
126
|
+
];
|
|
127
|
+
for (const [name, ok] of checks) console.log((ok ? "OK " : "BAD ") + name);
|
|
128
|
+
' "$RECV" "$line_no" "$own_tenant" "$own_token" "$other_tenant" "$other_token" >"$TMPDIR_EVAL/checks-$line_no.txt" 2>&1
|
|
129
|
+
while IFS= read -r line; do
|
|
130
|
+
[[ "$line" == OK* ]] && pass "$label: ${line#OK }" || fail "$label: ${line#BAD } — $(cat "$TMPDIR_EVAL/checks-$line_no.txt")"
|
|
131
|
+
done < <(grep -E '^(OK|BAD) ' "$TMPDIR_EVAL/checks-$line_no.txt")
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
echo "=== console tenant isolation (multi-tenant install proof, fa#410 item #5) ==="
|
|
135
|
+
|
|
136
|
+
: >"$RECV"
|
|
137
|
+
start_stub || fail "stub server did not start"
|
|
138
|
+
|
|
139
|
+
TOKEN_A="tok-a-alnum-111"
|
|
140
|
+
TOKEN_B="tok-b-alnum-222"
|
|
141
|
+
|
|
142
|
+
# ─── Phase 1 (tenant-a): install + emit, assert its own headers ────────────────────────────────
|
|
143
|
+
echo "--- 1. tenant-a: real install-console-config.sh writes telemetry.conf; emit carries tenant-a headers ---"
|
|
144
|
+
install_and_emit "tenant-a" "$TOKEN_A"
|
|
145
|
+
if wait_for_lines 1; then pass "tenant-a event POSTed to the stub"; else fail "tenant-a event did NOT POST within timeout"; fi
|
|
146
|
+
check_capture 1 "tenant-a" "tenant-a" "$TOKEN_A" "tenant-b" "$TOKEN_B"
|
|
147
|
+
|
|
148
|
+
# ─── Phase 2 (tenant-b): fresh conf + distinct token, assert its own headers ───────────────────
|
|
149
|
+
echo "--- 2. tenant-b: fresh conf + distinct token; emit carries tenant-b headers ---"
|
|
150
|
+
install_and_emit "tenant-b" "$TOKEN_B"
|
|
151
|
+
if wait_for_lines 2; then pass "tenant-b event POSTed to the stub"; else fail "tenant-b event did NOT POST within timeout"; fi
|
|
152
|
+
check_capture 2 "tenant-b" "tenant-b" "$TOKEN_B" "tenant-a" "$TOKEN_A"
|
|
153
|
+
|
|
154
|
+
# ─── Isolation: re-check phase 1's record after phase 2 ran, to catch any late cross-write ─────
|
|
155
|
+
echo "--- 3. isolation: tenant-a's captured request still never carries tenant-b's values (and vice versa, above) ---"
|
|
156
|
+
check_capture 1 "tenant-a (post phase-2 recheck)" "tenant-a" "$TOKEN_A" "tenant-b" "$TOKEN_B"
|
|
157
|
+
|
|
158
|
+
stop_stub
|
|
159
|
+
|
|
160
|
+
echo ""
|
|
161
|
+
if [[ "$errors" -eq 0 ]]; then
|
|
162
|
+
echo "test_console_tenant_isolation: all checks passed."
|
|
163
|
+
exit 0
|
|
164
|
+
else
|
|
165
|
+
echo "test_console_tenant_isolation: $errors check(s) failed."
|
|
166
|
+
exit 1
|
|
167
|
+
fi
|
|
@@ -81,7 +81,10 @@ if [[ $? -eq 0 ]]; then _pass "(a) trust-reconcile exits 0 (resolved session con
|
|
|
81
81
|
echo ""
|
|
82
82
|
echo "=== (b) HEADLINE --flow-id builder.build/verify: mixed evidence + critique → reconcile 0 ==="
|
|
83
83
|
B_AROOT="$TMP/b/aroot"; B_SLUG="flowid-converge"; B_DIR="$B_AROOT/$B_SLUG"
|
|
84
|
-
seed "$B_AROOT" "$B_SLUG" --flow-id builder.build
|
|
84
|
+
seed "$B_AROOT" "$B_SLUG" --flow-id builder.build
|
|
85
|
+
flow_agents_node "$WRITER" advance-state "$B_DIR" --status in_progress --phase verification \
|
|
86
|
+
--summary "Testing at verify." --next-action "Record evidence." \
|
|
87
|
+
--flow-definition builder.build --timestamp "2026-07-01T00:00:30Z" >/dev/null 2>&1
|
|
85
88
|
flow_agents_node "$WRITER" record-evidence "$B_DIR" --verdict pass \
|
|
86
89
|
--check-json '{"id":"k-diff","kind":"diff","status":"pass","summary":"diff excerpt"}' \
|
|
87
90
|
--check-json '{"id":"k-policy","kind":"policy","status":"pass","summary":"policy rule"}' \
|