@kontourai/flow-agents 3.2.0 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/add-to-project.yml +15 -0
- package/.github/workflows/ci.yml +173 -0
- package/CHANGELOG.md +54 -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/assignment-provider.js +10 -1
- 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-artifact-cleanup-audit.js +418 -11
- package/build/src/cli/workflow-sidecar.d.ts +310 -1
- package/build/src/cli/workflow-sidecar.js +1914 -126
- 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 +54 -1
- package/build/src/lib/flow-resolver.js +112 -5
- 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 +30 -3
- package/context/contracts/artifact-contract.md +16 -2
- 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 +194 -22
- 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/coordination-guide.md +370 -0
- package/docs/decisions/agent-coordination.md +26 -9
- package/docs/decisions/embeddable-engine.md +152 -0
- package/docs/decisions/index.md +5 -3
- package/docs/decisions/trust-ledger-retention.md +88 -0
- package/docs/decisions/trust-reconcile.md +42 -9
- package/docs/decisions/workflow-enforcement.md +31 -9
- package/docs/fixture-ownership.md +6 -2
- package/docs/implementing-trust-reconciliation.md +129 -0
- package/docs/index.md +23 -9
- package/docs/integrations/flow-agents-console.md +275 -0
- package/docs/integrations/index.md +4 -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-artifact-lifecycle.md +38 -1
- package/docs/workflow-usage-guide.md +10 -0
- package/evals/acceptance/prove-capture-teeth.sh +132 -0
- package/evals/ci/antigaming-suite.sh +2 -0
- package/evals/ci/run-baseline.sh +78 -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/reconcile-preflight/disputed-critique-unsuperseded.json +48 -0
- package/evals/fixtures/reconcile-preflight/standalone-disputed-session-local.json +59 -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_checkpoint_signing.sh +10 -2
- package/evals/integration/test_ci_actor_identity.sh +221 -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 +389 -2
- package/evals/integration/test_pull_work_board.sh +200 -0
- package/evals/integration/test_pull_work_provider.sh +1 -1
- package/evals/integration/test_reconcile_preflight.sh +304 -0
- 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_takeover_protocol.sh +340 -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 +121 -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_verify_hold.sh +910 -0
- package/evals/integration/test_veritas_governance_kit.sh +257 -0
- package/evals/integration/test_workflow_artifact_cleanup_audit.sh +575 -3
- 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 +37 -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/continue-work/SKILL.md +2 -0
- package/kits/builder/skills/deliver/SKILL.md +115 -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 +33 -2
- package/kits/builder/skills/release-readiness/SKILL.md +12 -0
- package/kits/knowledge/kit.json +9 -0
- package/kits/veritas-governance/docs/README.md +113 -7
- package/kits/veritas-governance/fixtures/exemption/approved.trust-bundle.json +74 -0
- package/kits/veritas-governance/fixtures/exemption/not-approved.trust-bundle.json +74 -0
- package/kits/veritas-governance/fixtures/exemption-review/mixed-fresh-stale.DECLARED.json +14 -0
- package/kits/veritas-governance/flows/exemption-issuance.flow.json +35 -0
- package/kits/veritas-governance/kit.json +19 -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 +214 -253
- package/scripts/hooks/codex-hook-adapter.js +77 -2
- package/scripts/hooks/evidence-capture.js +38 -5
- package/scripts/hooks/lib/actor-identity.js +82 -0
- 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 +194 -22
- 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/lib/reconcile-shape.js +381 -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/assignment-provider.ts +12 -1
- 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 +72 -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-artifact-cleanup-audit.ts +483 -10
- package/src/cli/workflow-sidecar.ts +1980 -119
- 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 +117 -4
- 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 +29 -3
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# test_ci_actor_identity.sh — CI-runtime actor identity tier (issue #398, extends #287; ADR 0021 §2).
|
|
3
|
+
#
|
|
4
|
+
# Proves a CI-triggered session gets a STABLE actor derived from the CI provider's published
|
|
5
|
+
# run/job identifiers, sitting ABOVE process-ancestry and BELOW an explicit override / native
|
|
6
|
+
# runtime session id in resolveActor()'s chain. The payoff: the #293 verify-hold gate now ENFORCES
|
|
7
|
+
# for CI sessions (stable identity) instead of degrading to advisory — the root fix for the CI
|
|
8
|
+
# false-block #293 had to work around.
|
|
9
|
+
#
|
|
10
|
+
# Part A — resolveActor() resolution (pure): stability + byte-identity, precedence (override /
|
|
11
|
+
# native runtime win), per-provider detection (GitHub, GitLab), conservative generic fallthrough,
|
|
12
|
+
# recognized-provider-missing-id fallthrough, and env-var injection sanitization.
|
|
13
|
+
# Part B — verify-hold integration: a STABLE CI actor BLOCKS a differing assignment-backed holder
|
|
14
|
+
# (ENFORCE — contrast the ancestry/advisory case in test_verify_hold.sh §1d), and a CI actor's
|
|
15
|
+
# OWN claim is recognized as self and PASSES (the reconstruction-seam fix in resolveEnsureSessionActor).
|
|
16
|
+
#
|
|
17
|
+
# Deterministic, no model spend, self-cleaning, no network, no `gh` process anywhere.
|
|
18
|
+
# Usage: bash evals/integration/test_ci_actor_identity.sh
|
|
19
|
+
|
|
20
|
+
set -uo pipefail
|
|
21
|
+
|
|
22
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
23
|
+
source "$ROOT/evals/lib/node.sh"
|
|
24
|
+
|
|
25
|
+
WRITER="workflow-sidecar"
|
|
26
|
+
ACTOR_IDENTITY_HELPER="$ROOT/scripts/hooks/lib/actor-identity.js"
|
|
27
|
+
|
|
28
|
+
TMPDIR_EVAL="$(mktemp -d)"
|
|
29
|
+
trap 'rm -rf "$TMPDIR_EVAL"' EXIT
|
|
30
|
+
ARTIFACT_ROOT="$TMPDIR_EVAL/artifact-root"
|
|
31
|
+
|
|
32
|
+
errors=0
|
|
33
|
+
pass() { echo " [PASS] $1"; }
|
|
34
|
+
fail() { echo " [FAIL] $1"; errors=$((errors + 1)); }
|
|
35
|
+
|
|
36
|
+
json_query() {
|
|
37
|
+
node -e 'const fs=require("fs"); let cur=JSON.parse(fs.readFileSync(process.argv[1],"utf8")); for (const part of process.argv[2].split(".")) cur=part==="length" ? cur.length : (Array.isArray(cur) ? cur[Number(part)] : cur[part]); console.log(cur);' "$1" "$2"
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# resolve_field <field:actor|source> <env-assignments...> — run resolveActor under a CLEAN env
|
|
41
|
+
# (only the passed assignments + HOME) so no ambient FLOW_AGENTS_ACTOR/CLAUDE_CODE_SESSION_ID from
|
|
42
|
+
# the eval host leaks into the resolution. Prints the requested field.
|
|
43
|
+
resolve_field() {
|
|
44
|
+
local field="$1"; shift
|
|
45
|
+
# env -i gives a CLEAN slate (no ambient FLOW_AGENTS_ACTOR / CLAUDE_CODE_SESSION_ID / real
|
|
46
|
+
# GITHUB_* from the CI host leaking in); PATH is preserved so `node` resolves.
|
|
47
|
+
env -i HOME="$HOME" PATH="$PATH" "$@" node -e '
|
|
48
|
+
const { resolveActor } = require(process.argv[1]);
|
|
49
|
+
const r = resolveActor(process.env);
|
|
50
|
+
process.stdout.write(process.argv[2] === "source" ? r.source : r.actor);
|
|
51
|
+
' "$ACTOR_IDENTITY_HELPER" "$field"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# verify_hold_under_ci <dir> <KEY=VAL...> — run verify-hold with a CONTROLLED CI identity.
|
|
55
|
+
# flow_agents_node is a bash FUNCTION (from node.sh), so it cannot be launched via `env -i`; instead
|
|
56
|
+
# run in a subshell that unsets every identity source that would out-rank the CI tier (an explicit
|
|
57
|
+
# override or a native runtime session id — including the eval host's own, since this suite itself
|
|
58
|
+
# runs under a coding agent) and exports the provided CI vars. When this suite runs in real GitHub
|
|
59
|
+
# Actions, the exported fake ids override the ambient real ones so the assertions stay deterministic.
|
|
60
|
+
verify_hold_under_ci() {
|
|
61
|
+
local dir="$1"; shift
|
|
62
|
+
(
|
|
63
|
+
unset FLOW_AGENTS_ACTOR CLAUDE_CODE_SESSION_ID CODEX_SESSION_ID OPENCODE_SESSION_ID PI_SESSION_ID CLAUDECODE
|
|
64
|
+
while [[ $# -gt 0 ]]; do export "${1?}"; shift; done
|
|
65
|
+
flow_agents_node "$WRITER" verify-hold "$dir"
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# append_liveness_event / write_assignment_record — the proven fixture writers from
|
|
70
|
+
# test_verify_hold.sh (kept byte-identical in shape so the join fixtures behave the same).
|
|
71
|
+
append_liveness_event() {
|
|
72
|
+
local root="$1" subject="$2" actor="$3" at="$4" ttl="${5:-1800}"
|
|
73
|
+
mkdir -p "$root/liveness"
|
|
74
|
+
node -e '
|
|
75
|
+
const fs = require("fs");
|
|
76
|
+
const evt = { type: "claim", subjectId: process.argv[1], actor: process.argv[2], at: process.argv[3], ttlSeconds: Number(process.argv[4]) };
|
|
77
|
+
fs.appendFileSync(process.argv[5], JSON.stringify(evt) + "\n");
|
|
78
|
+
' "$subject" "$actor" "$at" "$ttl" "$root/liveness/events.jsonl"
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
write_assignment_record() {
|
|
82
|
+
local root="$1" slug="$2" sessionId="$3" actorKey="$4"
|
|
83
|
+
mkdir -p "$root/assignment"
|
|
84
|
+
node -e '
|
|
85
|
+
const fs = require("fs");
|
|
86
|
+
const [dest, slug, sessionId, actorKey] = process.argv.slice(1);
|
|
87
|
+
const now = new Date().toISOString();
|
|
88
|
+
const actor = { runtime: "github-actions", session_id: sessionId, host: "eval-host" };
|
|
89
|
+
const rec = {
|
|
90
|
+
schema_version: "1.0", role: "AssignmentClaimRecord", subject_id: slug,
|
|
91
|
+
actor, actor_key: actorKey, claimed_at: now, ttl_seconds: 1800, branch: "main",
|
|
92
|
+
artifact_dir: slug, status: "claimed",
|
|
93
|
+
audit_trail: [{ at: now, transition: "claim", from_actor: null, to_actor: actor, reason: "claim" }],
|
|
94
|
+
};
|
|
95
|
+
fs.writeFileSync(dest, JSON.stringify(rec, null, 2));
|
|
96
|
+
' "$root/assignment/$slug.json" "$slug" "$sessionId" "$actorKey"
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# GitHub Actions env used across the file (a single job's stable identifiers).
|
|
100
|
+
GHA=(GITHUB_ACTIONS=true GITHUB_RUN_ID=555 GITHUB_RUN_ATTEMPT=1 GITHUB_JOB=deliver)
|
|
101
|
+
|
|
102
|
+
echo "=== CI actor identity (#398) ==="
|
|
103
|
+
|
|
104
|
+
# ─── Part A: resolveActor() resolution ───────────────────────────────────────────────────────
|
|
105
|
+
echo "--- A1. GitHub Actions -> stable ci-runtime actor, byte-identical across invocations ---"
|
|
106
|
+
GH_SRC="$(resolve_field source "${GHA[@]}")"
|
|
107
|
+
GH_ACTOR_1="$(resolve_field actor "${GHA[@]}")"
|
|
108
|
+
GH_ACTOR_2="$(resolve_field actor "${GHA[@]}")"
|
|
109
|
+
[[ "$GH_SRC" == "ci-runtime:github-actions" ]] && pass "source is ci-runtime:github-actions" || fail "source was not ci-runtime:github-actions: '$GH_SRC'"
|
|
110
|
+
[[ "$GH_ACTOR_1" == github-actions:555-1-deliver:* ]] && pass "actor is github-actions:<run>-<attempt>-<job>:<host> ('$GH_ACTOR_1')" || fail "actor shape unexpected: '$GH_ACTOR_1'"
|
|
111
|
+
[[ "$GH_ACTOR_1" == "$GH_ACTOR_2" ]] && pass "actor is byte-identical across repeated invocations (stable)" || fail "actor differed across invocations: '$GH_ACTOR_1' vs '$GH_ACTOR_2'"
|
|
112
|
+
|
|
113
|
+
echo "--- A2. precedence: explicit override and native runtime session id both WIN over CI ---"
|
|
114
|
+
OVR_SRC="$(resolve_field source "${GHA[@]}" FLOW_AGENTS_ACTOR=alice)"
|
|
115
|
+
[[ "$OVR_SRC" == "explicit-override" ]] && pass "FLOW_AGENTS_ACTOR under CI still resolves explicit-override (CI does not win)" || fail "override did not win under CI: '$OVR_SRC'"
|
|
116
|
+
NATIVE_SRC="$(resolve_field source "${GHA[@]}" CLAUDE_CODE_SESSION_ID=sess-xyz)"
|
|
117
|
+
[[ "$NATIVE_SRC" == runtime-session-id:* ]] && pass "native runtime session id under CI still resolves runtime-session-id (CI does not win)" || fail "native runtime id did not win under CI: '$NATIVE_SRC'"
|
|
118
|
+
|
|
119
|
+
echo "--- A3. GitLab CI -> stable gitlab-ci actor from CI_JOB_ID ---"
|
|
120
|
+
GL_SRC="$(resolve_field source GITLAB_CI=true CI_JOB_ID=9988)"
|
|
121
|
+
GL_ACTOR="$(resolve_field actor GITLAB_CI=true CI_JOB_ID=9988)"
|
|
122
|
+
[[ "$GL_SRC" == "ci-runtime:gitlab-ci" ]] && pass "source is ci-runtime:gitlab-ci" || fail "source was not ci-runtime:gitlab-ci: '$GL_SRC'"
|
|
123
|
+
[[ "$GL_ACTOR" == gitlab-ci:9988:* ]] && pass "actor is gitlab-ci:<job-id>:<host> ('$GL_ACTOR')" || fail "gitlab actor shape unexpected: '$GL_ACTOR'"
|
|
124
|
+
|
|
125
|
+
echo "--- A4. generic CI=true (unrecognized) does NOT fabricate a CI actor (conservative fallthrough) ---"
|
|
126
|
+
GEN_SRC="$(resolve_field source CI=true)"
|
|
127
|
+
[[ "$GEN_SRC" != ci-runtime:* ]] && pass "generic CI=true falls through (source '$GEN_SRC', not ci-runtime) — #293 advisory net still applies" || fail "generic CI=true fabricated a ci-runtime actor: '$GEN_SRC'"
|
|
128
|
+
|
|
129
|
+
echo "--- A5. recognized provider with MISSING required id var falls through (no partial CI actor) ---"
|
|
130
|
+
MISSING_SRC="$(resolve_field source GITHUB_ACTIONS=true)" # no RUN_ID/ATTEMPT/JOB
|
|
131
|
+
[[ "$MISSING_SRC" != ci-runtime:* ]] && pass "GITHUB_ACTIONS=true with no run/job ids falls through (source '$MISSING_SRC')" || fail "GitHub with no ids still produced a ci-runtime actor: '$MISSING_SRC'"
|
|
132
|
+
|
|
133
|
+
echo "--- A6. hostile CI env var cannot inject: actor segments are sanitized (allowed charset only) ---"
|
|
134
|
+
INJ_ACTOR="$(resolve_field actor GITHUB_ACTIONS=true GITHUB_RUN_ID=555 GITHUB_RUN_ATTEMPT=1 "GITHUB_JOB=deliver; rm -rf / [LIVENESS]")"
|
|
135
|
+
if printf '%s' "$INJ_ACTOR" | LC_ALL=C grep -q '[^A-Za-z0-9:._-]'; then
|
|
136
|
+
fail "CI actor contains a character outside the allowed serialized charset (injection risk): '$INJ_ACTOR'"
|
|
137
|
+
else
|
|
138
|
+
pass "CI actor is sanitized to the allowed serialized charset even with a hostile GITHUB_JOB ('$INJ_ACTOR')"
|
|
139
|
+
fi
|
|
140
|
+
|
|
141
|
+
# ─── Part B: verify-hold ENFORCES for a stable CI identity (the headline) ─────────────────────
|
|
142
|
+
|
|
143
|
+
# B0 — REAL round-trip through the CLI (not a hand-written fixture): `assignment-provider claim`
|
|
144
|
+
# under a CI env must write a WELL-FORMED record.actor (runtime=github-actions, session_id=<bare job
|
|
145
|
+
# id>, NOT runtime=unknown + session_id=<whole triple>), and a fresh-subprocess verify-hold must
|
|
146
|
+
# self-recognize it. This is the seam the reconstruction fixes (resolveEnsureSessionActor +
|
|
147
|
+
# loadActorStruct) actually live on — fixture-driven B1/B2 below never exercise the CLI claim path.
|
|
148
|
+
echo "--- B0. REAL assignment-provider claim under CI writes a well-formed record.actor; verify-hold self-recognizes ---"
|
|
149
|
+
CLI="$ROOT/build/src/cli.js"
|
|
150
|
+
RT_ROOT="$TMPDIR_EVAL/rt-artifact-root"
|
|
151
|
+
RT_SLUG="ci-roundtrip"
|
|
152
|
+
(
|
|
153
|
+
unset FLOW_AGENTS_ACTOR CLAUDE_CODE_SESSION_ID CODEX_SESSION_ID OPENCODE_SESSION_ID PI_SESSION_ID CLAUDECODE
|
|
154
|
+
export "${GHA[@]}"
|
|
155
|
+
node "$CLI" assignment-provider claim --provider local-file --artifact-root "$RT_ROOT" \
|
|
156
|
+
--subject-id "$RT_SLUG" --branch main --artifact-dir "$RT_SLUG"
|
|
157
|
+
) >"$TMPDIR_EVAL/rt-claim.out" 2>"$TMPDIR_EVAL/rt-claim.err"
|
|
158
|
+
RT_REC="$RT_ROOT/assignment/$RT_SLUG.json"
|
|
159
|
+
if [[ -f "$RT_REC" ]]; then
|
|
160
|
+
pass "assignment-provider claim under CI wrote a record"
|
|
161
|
+
RT_RUNTIME="$(json_query "$RT_REC" "actor.runtime")"
|
|
162
|
+
RT_SESSION="$(json_query "$RT_REC" "actor.session_id")"
|
|
163
|
+
RT_KEY="$(json_query "$RT_REC" "actor_key")"
|
|
164
|
+
[[ "$RT_RUNTIME" == "github-actions" ]] && pass "record.actor.runtime is github-actions (NOT 'unknown' — F1 reconstruction fix)" || fail "record.actor.runtime was '$RT_RUNTIME', expected github-actions (F1 regressed: loadActorStruct not CI-aware)"
|
|
165
|
+
[[ "$RT_SESSION" == "555-1-deliver" ]] && pass "record.actor.session_id is the bare CI job id 555-1-deliver (NOT the whole triple — F1 fix)" || fail "record.actor.session_id was '$RT_SESSION', expected 555-1-deliver (F1 regressed)"
|
|
166
|
+
[[ "$RT_KEY" == github-actions:555-1-deliver:* ]] && pass "record.actor_key is the canonical CI key ('$RT_KEY')" || fail "record.actor_key was '$RT_KEY', expected github-actions:555-1-deliver:<host>"
|
|
167
|
+
else
|
|
168
|
+
fail "assignment-provider claim under CI wrote NO record: $(cat "$TMPDIR_EVAL/rt-claim.out" "$TMPDIR_EVAL/rt-claim.err")"
|
|
169
|
+
fi
|
|
170
|
+
# Fresh-subprocess verify-hold under the same CI env → the actor written by the real claim is self.
|
|
171
|
+
mkdir -p "$RT_ROOT/$RT_SLUG"
|
|
172
|
+
if verify_hold_under_ci "$RT_ROOT/$RT_SLUG" "${GHA[@]}" >"$TMPDIR_EVAL/rt-vh.out" 2>"$TMPDIR_EVAL/rt-vh.err"; then
|
|
173
|
+
pass "verify-hold self-recognizes the CI actor from a REAL claim across subprocesses (end-to-end seam)"
|
|
174
|
+
else
|
|
175
|
+
fail "verify-hold FALSE-BLOCKED the CI actor's own real claim across subprocesses: $(cat "$TMPDIR_EVAL/rt-vh.out" "$TMPDIR_EVAL/rt-vh.err")"
|
|
176
|
+
fi
|
|
177
|
+
[[ "$(json_query "$TMPDIR_EVAL/rt-vh.out" "reason")" == "self_is_holder" ]] && pass "real-round-trip verify-hold reason is self_is_holder" || fail "real-round-trip verify-hold reason was not self_is_holder: $(cat "$TMPDIR_EVAL/rt-vh.out")"
|
|
178
|
+
|
|
179
|
+
echo "--- B1. a stable CI actor BLOCKS a differing, assignment-backed holder (ENFORCE, not advisory) ---"
|
|
180
|
+
BLOCK_SLUG="ci-verify-hold-block"
|
|
181
|
+
BLOCK_DIR="$ARTIFACT_ROOT/$BLOCK_SLUG"
|
|
182
|
+
mkdir -p "$BLOCK_DIR"
|
|
183
|
+
# Held (fresh) by a clearly different actor -> a durable, assignment-backed conflict.
|
|
184
|
+
write_assignment_record "$ARTIFACT_ROOT" "$BLOCK_SLUG" "other-session" "eval-actor-ci-other-holder"
|
|
185
|
+
append_liveness_event "$ARTIFACT_ROOT" "$BLOCK_SLUG" "eval-actor-ci-other-holder" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" 1800
|
|
186
|
+
# Current actor: a STABLE CI identity (GitHub env), NO FLOW_AGENTS_ACTOR — before #398 this would
|
|
187
|
+
# have fallen to ancestry (unstable) and the gate would have degraded to advisory/PASS.
|
|
188
|
+
if verify_hold_under_ci "$BLOCK_DIR" "${GHA[@]}" >"$TMPDIR_EVAL/ci-block.out" 2>"$TMPDIR_EVAL/ci-block.err"; then
|
|
189
|
+
fail "verify-hold under a stable CI identity should BLOCK a differing assignment-backed holder (ENFORCE) — got exit 0: $(cat "$TMPDIR_EVAL/ci-block.out" "$TMPDIR_EVAL/ci-block.err")"
|
|
190
|
+
else
|
|
191
|
+
pass "verify-hold under a stable CI identity BLOCKS a differing assignment-backed holder (the #398 enforce payoff)"
|
|
192
|
+
fi
|
|
193
|
+
[[ "$(json_query "$TMPDIR_EVAL/ci-block.out" "ok")" == "false" ]] && pass "CI-block verify-hold JSON reports ok:false" || fail "CI-block verify-hold JSON did not report ok:false: $(cat "$TMPDIR_EVAL/ci-block.out")"
|
|
194
|
+
CI_BLOCK_REASON="$(json_query "$TMPDIR_EVAL/ci-block.out" "reason")"
|
|
195
|
+
[[ "$CI_BLOCK_REASON" != "actor-identity-unstable-advisory-only" ]] && pass "CI-block reason is NOT the unstable-advisory degradation ('$CI_BLOCK_REASON') — the gate genuinely enforces" || fail "CI actor still degraded to advisory (reason=$CI_BLOCK_REASON) — #398 did not make the CI identity stable"
|
|
196
|
+
|
|
197
|
+
echo "--- B2. a CI actor's OWN claim is recognized as self and PASSES (the reconstruction-seam fix) ---"
|
|
198
|
+
SELF_SLUG="ci-verify-hold-self"
|
|
199
|
+
SELF_DIR="$ARTIFACT_ROOT/$SELF_SLUG"
|
|
200
|
+
mkdir -p "$SELF_DIR"
|
|
201
|
+
# The holder's actor_key IS the CI actor resolveActor() produces under the GitHub env — so the
|
|
202
|
+
# claim written in one CI step is recognized as self at publish in a later step. If the
|
|
203
|
+
# resolveEnsureSessionActor reconstruction diverged (rebuilt an ancestry struct), this would
|
|
204
|
+
# FALSE-BLOCK — exactly the bug #398 removes.
|
|
205
|
+
write_assignment_record "$ARTIFACT_ROOT" "$SELF_SLUG" "self-session" "$GH_ACTOR_1"
|
|
206
|
+
if verify_hold_under_ci "$SELF_DIR" "${GHA[@]}" >"$TMPDIR_EVAL/ci-self.out" 2>"$TMPDIR_EVAL/ci-self.err"; then
|
|
207
|
+
pass "verify-hold PASSES when the CI actor is the holder (self recognized across subprocesses — seam fix)"
|
|
208
|
+
else
|
|
209
|
+
fail "verify-hold FALSE-BLOCKED a CI actor on its OWN claim (the reconstruction seam regressed): $(cat "$TMPDIR_EVAL/ci-self.out" "$TMPDIR_EVAL/ci-self.err")"
|
|
210
|
+
fi
|
|
211
|
+
[[ "$(json_query "$TMPDIR_EVAL/ci-self.out" "ok")" == "true" ]] && pass "CI-self verify-hold JSON reports ok:true" || fail "CI-self verify-hold JSON did not report ok:true: $(cat "$TMPDIR_EVAL/ci-self.out")"
|
|
212
|
+
[[ "$(json_query "$TMPDIR_EVAL/ci-self.out" "reason")" == "self_is_holder" ]] && pass "CI-self verify-hold reason is self_is_holder (the CI actor matched the stored actor_key)" || fail "CI-self verify-hold reason was not self_is_holder: $(cat "$TMPDIR_EVAL/ci-self.out")"
|
|
213
|
+
|
|
214
|
+
echo ""
|
|
215
|
+
if [[ "$errors" -eq 0 ]]; then
|
|
216
|
+
echo "ALL CI ACTOR IDENTITY CHECKS PASSED"
|
|
217
|
+
exit 0
|
|
218
|
+
else
|
|
219
|
+
echo "$errors CHECK(S) FAILED"
|
|
220
|
+
exit 1
|
|
221
|
+
fi
|
|
@@ -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"}' \
|
|
@@ -251,24 +251,30 @@ fi
|
|
|
251
251
|
echo ""
|
|
252
252
|
echo "── P-d declared-only: session WITH active_flow_id=builder.build / active_step_id=verify ──"
|
|
253
253
|
|
|
254
|
-
# Create a session
|
|
254
|
+
# Create a session at the declared first step, then use the transition surface to
|
|
255
|
+
# establish the verify-state fixture exercised by this producer test.
|
|
255
256
|
mkdir -p "$SESSION_ROOT"
|
|
256
257
|
if flow_agents_node "$WRITER" ensure-session \
|
|
257
258
|
--artifact-root "$SESSION_ROOT" \
|
|
258
259
|
--task-slug dual-emit-test \
|
|
259
260
|
--flow-id builder.build \
|
|
260
|
-
--step-id verify \
|
|
261
261
|
--title "Declared-Only Test" \
|
|
262
262
|
--summary "Test declared-only emit for ADR 0016 P-d." \
|
|
263
263
|
--criterion "Tests pass" \
|
|
264
264
|
--timestamp "2026-06-26T00:00:00Z" >"$TMP/ensure.out" 2>"$TMP/ensure.err"; then
|
|
265
|
-
_pass "ensure-session with --flow-id
|
|
265
|
+
_pass "ensure-session with --flow-id succeeds at the declared first step"
|
|
266
266
|
else
|
|
267
|
-
_fail "ensure-session with --flow-id
|
|
267
|
+
_fail "ensure-session with --flow-id failed: $(cat "$TMP/ensure.out" "$TMP/ensure.err")"
|
|
268
268
|
fi
|
|
269
269
|
|
|
270
270
|
DUAL_DIR="$SESSION_ROOT/dual-emit-test"
|
|
271
271
|
|
|
272
|
+
flow_agents_node "$WRITER" advance-state "$DUAL_DIR" \
|
|
273
|
+
--status in_progress --phase verification \
|
|
274
|
+
--summary "Testing declared-only verify claims." --next-action "Record evidence." \
|
|
275
|
+
--flow-definition builder.build \
|
|
276
|
+
--timestamp "2026-06-26T00:00:30Z" >/dev/null 2>&1
|
|
277
|
+
|
|
272
278
|
# Verify current.json carries the flow keys
|
|
273
279
|
if node -e "
|
|
274
280
|
const fs = require('fs');
|