@kontourai/flow-agents 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/CODEOWNERS +2 -0
- package/.github/workflows/ci.yml +4 -4
- package/.github/workflows/docs-pages.yml +1 -1
- package/.github/workflows/kit-gates-demo.yml +2 -2
- package/.github/workflows/publish-npm.yml +2 -2
- package/.github/workflows/runtime-compat.yml +2 -2
- package/.github/workflows/trust-reconcile.yml +1 -1
- package/CHANGELOG.md +29 -0
- package/build/src/cli/sidecar-claim-explain.d.ts +45 -0
- package/build/src/cli/sidecar-claim-explain.js +87 -0
- package/build/src/cli/telemetry-doctor.js +2 -4
- package/build/src/cli/usage-feedback.js +16 -8
- package/build/src/cli/workflow-artifact-cleanup-audit.js +4 -2
- package/build/src/cli/workflow-sidecar.d.ts +2 -44
- package/build/src/cli/workflow-sidecar.js +18 -87
- package/build/src/index.d.ts +1 -0
- package/build/src/index.js +1 -0
- package/build/src/lib/local-artifact-root.d.ts +11 -0
- package/build/src/lib/local-artifact-root.js +30 -0
- package/build/src/tools/validate-source-tree.js +1 -0
- package/context/scripts/telemetry/lib/config.sh +3 -4
- package/docs/adr/0018-freeze-local-shell-heuristics.md +95 -0
- package/docs/repository-structure.md +7 -3
- package/evals/integration/test_bundle_lifecycle.sh +9 -9
- package/evals/integration/test_gate_lockdown.sh +25 -6
- package/evals/integration/test_migrate_local_artifacts.sh +102 -0
- package/evals/integration/test_workflow_sidecar_writer.sh +34 -0
- package/evals/run.sh +2 -0
- package/evals/static/test_unit_helpers.sh +26 -0
- package/integrations/strands-ts/package.json +3 -0
- package/integrations/strands-ts/src/telemetry.ts +31 -50
- package/kits/knowledge/adapters/flow-runner/index.js +1 -1
- package/kits/knowledge/adapters/flow-runner/telemetry.js +2 -2
- package/package.json +2 -1
- package/scripts/README.md +1 -0
- package/scripts/ci/trust-reconcile.js +7 -23
- package/scripts/hooks/config-protection.js +6 -0
- package/scripts/hooks/evidence-capture.js +26 -47
- package/scripts/hooks/lib/local-artifact-paths.js +32 -0
- package/scripts/hooks/stop-goal-fit.js +37 -58
- package/scripts/hooks/workflow-steering.js +5 -3
- package/scripts/lib/command-log-chain.js +78 -0
- package/scripts/migrate-local-artifacts.mjs +230 -0
- package/scripts/repair-command-log.js +8 -15
- package/scripts/telemetry/lib/config.sh +3 -4
- package/src/cli/public-api.test.mjs +21 -0
- package/src/cli/sidecar-claim-explain.ts +130 -0
- package/src/cli/sidecar-pure-helpers.test.mjs +168 -0
- package/src/cli/telemetry-doctor.ts +2 -4
- package/src/cli/usage-feedback.ts +15 -8
- package/src/cli/workflow-artifact-cleanup-audit.ts +4 -2
- package/src/cli/workflow-sidecar.ts +17 -131
- package/src/index.ts +14 -0
- package/src/lib/local-artifact-root.ts +39 -0
- package/src/tools/validate-source-tree.ts +1 -0
package/build/src/index.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import * as path from "node:path";
|
|
17
17
|
import { loadJson as _loadJson, writeJson as _writeJson } from "./cli/workflow-sidecar.js";
|
|
18
|
+
export { defaultArtifactRootForRead, defaultTelemetryDirForRead, defaultTelemetryDirsForRead, firstExistingPath, flowAgentsArtifactRoot, KONTOURAI_DIR, legacyFlowAgentsArtifactRoot, LEGACY_FLOW_AGENTS_DIR, legacyTelemetryDataDir, LEGACY_TELEMETRY_DIR, telemetryDataDir, } from "./lib/local-artifact-root.js";
|
|
18
19
|
export {
|
|
19
20
|
// Trust-bundle (Hachure) validation — the same validator the writer uses.
|
|
20
21
|
validateTrustBundle,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const KONTOURAI_DIR = ".kontourai";
|
|
2
|
+
export declare const LEGACY_FLOW_AGENTS_DIR = ".flow-agents";
|
|
3
|
+
export declare const LEGACY_TELEMETRY_DIR = ".telemetry";
|
|
4
|
+
export declare function flowAgentsArtifactRoot(cwd?: string): string;
|
|
5
|
+
export declare function legacyFlowAgentsArtifactRoot(cwd?: string): string;
|
|
6
|
+
export declare function telemetryDataDir(cwd?: string): string;
|
|
7
|
+
export declare function legacyTelemetryDataDir(cwd?: string): string;
|
|
8
|
+
export declare function firstExistingPath(candidates: string[]): string;
|
|
9
|
+
export declare function defaultArtifactRootForRead(cwd?: string): string;
|
|
10
|
+
export declare function defaultTelemetryDirForRead(cwd?: string): string;
|
|
11
|
+
export declare function defaultTelemetryDirsForRead(cwd?: string): string[];
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
export const KONTOURAI_DIR = ".kontourai";
|
|
4
|
+
export const LEGACY_FLOW_AGENTS_DIR = ".flow-agents";
|
|
5
|
+
export const LEGACY_TELEMETRY_DIR = ".telemetry";
|
|
6
|
+
export function flowAgentsArtifactRoot(cwd = process.cwd()) {
|
|
7
|
+
return path.resolve(cwd, KONTOURAI_DIR, "flow-agents");
|
|
8
|
+
}
|
|
9
|
+
export function legacyFlowAgentsArtifactRoot(cwd = process.cwd()) {
|
|
10
|
+
return path.resolve(cwd, LEGACY_FLOW_AGENTS_DIR);
|
|
11
|
+
}
|
|
12
|
+
export function telemetryDataDir(cwd = process.cwd()) {
|
|
13
|
+
return path.resolve(cwd, KONTOURAI_DIR, "telemetry");
|
|
14
|
+
}
|
|
15
|
+
export function legacyTelemetryDataDir(cwd = process.cwd()) {
|
|
16
|
+
return path.resolve(cwd, LEGACY_TELEMETRY_DIR);
|
|
17
|
+
}
|
|
18
|
+
export function firstExistingPath(candidates) {
|
|
19
|
+
return candidates.find((candidate) => fs.existsSync(candidate)) ?? candidates[0];
|
|
20
|
+
}
|
|
21
|
+
export function defaultArtifactRootForRead(cwd = process.cwd()) {
|
|
22
|
+
return firstExistingPath([flowAgentsArtifactRoot(cwd), legacyFlowAgentsArtifactRoot(cwd)]);
|
|
23
|
+
}
|
|
24
|
+
export function defaultTelemetryDirForRead(cwd = process.cwd()) {
|
|
25
|
+
return firstExistingPath([telemetryDataDir(cwd), legacyTelemetryDataDir(cwd)]);
|
|
26
|
+
}
|
|
27
|
+
export function defaultTelemetryDirsForRead(cwd = process.cwd()) {
|
|
28
|
+
const dirs = [telemetryDataDir(cwd), legacyTelemetryDataDir(cwd)];
|
|
29
|
+
return dirs.filter((dir, index) => dirs.indexOf(dir) === index && fs.existsSync(dir));
|
|
30
|
+
}
|
|
@@ -74,6 +74,7 @@ const hookFilePolicies = new Map([
|
|
|
74
74
|
["scripts/hooks/lib/audit-transport.sh", { category: "shared hook library", requiredNeedles: ["audit_emit"] }],
|
|
75
75
|
["scripts/hooks/lib/hook-flags.js", { category: "shared hook library", requiredNeedles: ["isHookEnabled"] }],
|
|
76
76
|
["scripts/hooks/lib/liveness-read.js", { category: "shared hook library", requiredNeedles: ["freshHolders", "readLivenessEvents"] }],
|
|
77
|
+
["scripts/hooks/lib/local-artifact-paths.js", { category: "shared hook library", requiredNeedles: ["flowAgentsArtifactRoot", "defaultArtifactRootForRead"] }],
|
|
77
78
|
["scripts/hooks/lib/patterns.sh", { category: "shared hook library", requiredNeedles: ["_detect_secrets"] }],
|
|
78
79
|
["scripts/hooks/lib/resolve-formatter.js", { category: "shared hook library", requiredNeedles: ["resolveFormatter"] }],
|
|
79
80
|
]);
|
|
@@ -7,10 +7,9 @@ TELEMETRY_CONFIG_FILE="${TELEMETRY_CONFIG_FILE:-${TELEMETRY_DIR}/telemetry.conf}
|
|
|
7
7
|
# Defaults
|
|
8
8
|
TELEMETRY_ENABLED="${TELEMETRY_ENABLED:-true}"
|
|
9
9
|
# TELEMETRY_DIR is <workspace>/scripts/telemetry, so the workspace root is
|
|
10
|
-
# two levels up.
|
|
11
|
-
#
|
|
12
|
-
|
|
13
|
-
TELEMETRY_DATA_DIR="${TELEMETRY_DATA_DIR:-$(cd "${TELEMETRY_DIR}/../.." && pwd)/.telemetry}"
|
|
10
|
+
# two levels up. Local runtime telemetry defaults under .kontourai/telemetry;
|
|
11
|
+
# explicit TELEMETRY_DATA_DIR still wins.
|
|
12
|
+
TELEMETRY_DATA_DIR="${TELEMETRY_DATA_DIR:-$(cd "${TELEMETRY_DIR}/../.." && pwd)/.kontourai/telemetry}"
|
|
14
13
|
TELEMETRY_SESSION_DIR="${TELEMETRY_SESSION_DIR:-${TELEMETRY_DATA_DIR}/sessions}"
|
|
15
14
|
TELEMETRY_ENRICH_SYSTEM="${TELEMETRY_ENRICH_SYSTEM:-true}"
|
|
16
15
|
TELEMETRY_ENRICH_WORKSPACE="${TELEMETRY_ENRICH_WORKSPACE:-true}"
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "ADR 0018: Freeze the Local Shell-Parsing Heuristics; Route New Enforcement to the CI Anchor"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# ADR 0018: Freeze the Local Shell-Parsing Heuristics; Route New Enforcement to the CI Anchor
|
|
6
|
+
|
|
7
|
+
Status: Accepted
|
|
8
|
+
|
|
9
|
+
## Context
|
|
10
|
+
|
|
11
|
+
ADR 0017 records the anti-gaming trust model: a **layered** defense whose *authoritative*
|
|
12
|
+
boundary is the external CI anchor (`trust-reconcile.js` + the required anti-gaming suite),
|
|
13
|
+
with the local Stop gate and `config-protection.js` as **Layer 1 bar-raisers** — explicitly
|
|
14
|
+
"a bar-raiser, not a wall." That model was earned by a 7-round adversarial loop (PRs
|
|
15
|
+
#213–#228), and ADR 0017 states the loop is "run to convergence."
|
|
16
|
+
|
|
17
|
+
The local shell-parsing heuristics have nonetheless kept accreting per-round string-matching
|
|
18
|
+
rules:
|
|
19
|
+
|
|
20
|
+
- `config-protection.js` — redirect detection (`>`, `>>`, `tee`), interpreter-write
|
|
21
|
+
detection (`node -e`, `py -c`, `sed -i`, `perl -e`), and `cp`/`mv`/`install` target
|
|
22
|
+
matching, grown across R5a / R6 / R7.
|
|
23
|
+
- `hasLaunderingOperator` (now in `scripts/lib/command-log-chain.js`) — the exit-code-mask
|
|
24
|
+
screen, extended in R6a / R7.
|
|
25
|
+
|
|
26
|
+
Their **own headers already admit the ceiling**: *"An agent with shell access can still
|
|
27
|
+
evade. The real anchor is external."* Each new round adds another regex for another evasion
|
|
28
|
+
shape (runtime-constructed paths, base64, exotic tools, here-docs, …) while the set remains,
|
|
29
|
+
by construction, non-exhaustive. This is **low-ROI churn** that also manufactures *false
|
|
30
|
+
confidence* in a control ADR 0017 already classifies as best-effort.
|
|
31
|
+
|
|
32
|
+
Worse, the pattern-chasing has a real cost beyond effort: the blanket `||` rule in
|
|
33
|
+
`hasLaunderingOperator` flags **any** `||` in a claimed verification command, which
|
|
34
|
+
**false-positives legitimate commands** that use `||` for control flow rather than exit-code
|
|
35
|
+
laundering — e.g. `test -d node_modules || npm ci`, `command -v shellcheck || npm i -g shellcheck`.
|
|
36
|
+
It errs toward blocking, which is acceptable for a bar-raiser but not something to keep
|
|
37
|
+
extending.
|
|
38
|
+
|
|
39
|
+
## Decision
|
|
40
|
+
|
|
41
|
+
**Freeze the local shell-parsing heuristics as feature-complete bar-raisers.** Specifically:
|
|
42
|
+
|
|
43
|
+
1. **No new evasion-pattern rules** are added to `config-protection.js`'s redirect/
|
|
44
|
+
interpreter/copy detection or to `hasLaunderingOperator`. The current rule sets are the
|
|
45
|
+
frozen surface. They stand as documented, intentionally-incomplete local bar-raisers.
|
|
46
|
+
|
|
47
|
+
2. **New enforcement concerns route to the external anchor**, not to new local regex:
|
|
48
|
+
- a new way to launder a verification result → strengthen the CI **`trust-reconcile`**
|
|
49
|
+
reconciliation and add a case to the required **anti-gaming suite**
|
|
50
|
+
(`evals/ci/antigaming-suite.sh`);
|
|
51
|
+
- a new self-tamper / kill-switch vector → it is already defended structurally by Layer 2
|
|
52
|
+
(CI does not trust the agent's files or environment) and Layer 4 (CODEOWNERS + required
|
|
53
|
+
checks). Encode the *regression test* there, not another local path matcher.
|
|
54
|
+
|
|
55
|
+
3. **Still allowed** under the freeze: correctness/bug fixes that do **not** expand the
|
|
56
|
+
pattern surface (e.g. removing a clear false-positive, consolidating duplicated copies as
|
|
57
|
+
in ops#20), and changes required to keep the existing rules working. The freeze targets
|
|
58
|
+
*scope growth*, not maintenance.
|
|
59
|
+
|
|
60
|
+
The decisive line — already the consistent finding of ADR 0017 — is that a local control
|
|
61
|
+
running inside the agent's environment cannot be made airtight by adding rules; effort spent
|
|
62
|
+
making the bar-raiser taller has sharply diminishing returns once the external anchor exists.
|
|
63
|
+
|
|
64
|
+
## Accepted limitations (documented, not closed)
|
|
65
|
+
|
|
66
|
+
These are now **accepted properties** of the frozen Layer 1, with the CI anchor as the
|
|
67
|
+
authoritative backstop:
|
|
68
|
+
|
|
69
|
+
- **`config-protection.js` — incomplete coverage.** Runtime-constructed protected paths
|
|
70
|
+
(`homedir() + '/.bashrc'`), base64/multi-step path assembly, interpreters outside the
|
|
71
|
+
listed set (ruby, php, …), `rsync`/`dd`/process-substitution writes, and here-docs are
|
|
72
|
+
**not** caught. *Backstop:* Layer 2 does not trust those files; Layer 4 + human review.
|
|
73
|
+
- **`hasLaunderingOperator` — blanket `||` over-blocks.** A legitimate verification command
|
|
74
|
+
that uses `||` for control flow is flagged as laundering. *Backstop / workaround:* split
|
|
75
|
+
the conditional out of the single claimed-verify command; CI reconciles the real result
|
|
76
|
+
regardless. We keep the blanket rule (fail-toward-blocking) rather than enumerate "safe"
|
|
77
|
+
`||` forms — enumeration is exactly the churn this ADR freezes.
|
|
78
|
+
|
|
79
|
+
## Consequences
|
|
80
|
+
|
|
81
|
+
- The adversarial-hardening loop is declared **converged at the local layer**: ongoing trust
|
|
82
|
+
investment goes to the external anchor (re-run freshness, reconciliation breadth, the
|
|
83
|
+
required suite, activation of `Trust Reconcile` as a no-bypass check — ADR 0017 §Activation,
|
|
84
|
+
issue #225), not to more local string parsing.
|
|
85
|
+
- Reviewers can decline new "Round N" local-heuristic PRs by pointing here: the bar-raiser is
|
|
86
|
+
frozen by decision, and the gap belongs in the CI anchor.
|
|
87
|
+
- No behavior change ships with this ADR. The heuristics keep doing exactly what they do; we
|
|
88
|
+
stop growing them and we write down why.
|
|
89
|
+
|
|
90
|
+
## Related
|
|
91
|
+
|
|
92
|
+
ADR 0017 (anti-gaming trust security model — the layered defense this freezes the local
|
|
93
|
+
layer of), ADR 0016 (three-hard-boundary model), ADR 0010 (workflow trust state as a hachure
|
|
94
|
+
bundle). Implements ops#21. Consolidation of the frozen helpers: ops#20
|
|
95
|
+
(`scripts/lib/command-log-chain.js`).
|
|
@@ -10,9 +10,10 @@ This is the canonical developer-facing map for the Flow Agents repository. Use i
|
|
|
10
10
|
|
|
11
11
|
- Edit canonical source in the repo root areas listed below, then regenerate derived output with the documented commands.
|
|
12
12
|
- Do not edit `dist/`, `build/`, or `_site/` by hand. They are generated from tracked source.
|
|
13
|
-
- Do not commit local runtime state from `.flow-agents/<slug>/`, `.codex/`, `.claude/`, `.omx/`, `.promptfoo/`, `.telemetry/`, `.surface/`, `.veritas
|
|
13
|
+
- Do not commit local runtime state from `.flow-agents/<slug>/`, `.kontourai/`, `.codex/`, `.claude/`, `.omx/`, `.promptfoo/`, `.telemetry/`, `.surface/`, generated `.veritas/` output, or tool caches; intentionally tracked `.veritas/` governance/config remains durable source.
|
|
14
14
|
- Runtime workflow artifacts stay local and ignored; promote reviewable or durable outcomes to docs, source, schemas, or provider records before merging to `main`.
|
|
15
15
|
- Treat generated exports and installed runtime config as products of `packaging/manifest.json`, `src/tools/build-universal-bundles.ts`, `scripts/install-*.sh`, and the source directories they copy.
|
|
16
|
+
- Use `.kontourai/` for non-durable local or generated Kontour workspace state. Keep durable tracked files in intuitive product-owned paths such as `.veritas/`, `.flow/`, `.agents/`, `.claude/commands`, and `docs/`; existing transition ignores remain during migration.
|
|
16
17
|
|
|
17
18
|
## Target Layout
|
|
18
19
|
|
|
@@ -32,7 +33,7 @@ This is the canonical developer-facing map for the Flow Agents repository. Use i
|
|
|
32
33
|
docs/ # durable docs and GitHub Pages source
|
|
33
34
|
integrations/ # optional external integration config
|
|
34
35
|
dist/ build/ _site/ # generated output; ignored
|
|
35
|
-
.flow-agents/ .codex/ .claude/ ... # local runtime state; ignored by default
|
|
36
|
+
.flow-agents/ .kontourai/ .codex/ .claude/ ... # local runtime state; ignored by default
|
|
36
37
|
```
|
|
37
38
|
|
|
38
39
|
## Top-Level Inventory
|
|
@@ -40,11 +41,12 @@ This is the canonical developer-facing map for the Flow Agents repository. Use i
|
|
|
40
41
|
| Path | Classification | Source of truth | Generated or runtime policy | Safe cleanup rule |
|
|
41
42
|
| --- | --- | --- | --- | --- |
|
|
42
43
|
| `.flow-agents/` | runtime state | Workflow tools write local session artifacts. | Ignored. | Do not commit task runtime roots; promote durable decisions to docs, source, schemas, or providers before merge. |
|
|
44
|
+
| `.kontourai/` | local/generated workspace state | Local Kontour tools and developer workflows. | Ignored. | Safe local cleanup when no active workflow needs it; durable source and decisions stay in product-owned tracked paths. |
|
|
43
45
|
| `.claude/` | installed runtime config | Generated bundle or local runtime install. | Ignored. | Reinstall from `dist/claude-code/` instead of editing as source. |
|
|
44
46
|
| `.codex/` | installed runtime config | Generated bundle or local runtime install. | Ignored. | Reinstall from `dist/codex/` or `scripts/install-codex-home.sh`; do not treat local hooks as canonical. |
|
|
45
47
|
| `.githooks/` | canonical repo tooling | Tracked repository hook scripts. | Source, not runtime agent hooks. | Keep compatible with `npm run setup:repo-hooks` and `npm run validate:repo-hooks --`. |
|
|
46
48
|
| `.github/` | canonical CI config | GitHub workflow files. | Source. | Preserve workflow command names and artifact expectations. |
|
|
47
|
-
| `.ai/`, `.omx/`, `.promptfoo/`, `.surface/`, `.telemetry/`, `.veritas/` | runtime, cache, or integration output | Local tools and
|
|
49
|
+
| `.ai/`, `.omx/`, `.promptfoo/`, `.surface/`, `.telemetry/`, `.veritas/` | runtime, cache, or integration output | Local tools, optional integrations, and generated Veritas output. | Ignored runtime state, except intentionally tracked Veritas governance/config. | Clean locally when not needed; promote only stable integration config under `integrations/` or durable docs. |
|
|
48
50
|
| `.venv/`, `node_modules/`, `test-results/`, `__pycache__/` | dependency/cache output | Package managers and test tools. | Ignored. | Safe local cleanup; recreate with normal install or test commands. |
|
|
49
51
|
| `_site/` | generated docs output | Built from `docs/`. | Ignored. | Recreate with docs preview/build tooling. |
|
|
50
52
|
| `agent-cards/` | canonical source | Discovery metadata for routable agents. | Exported into runtime bundles. | Do not delete without checking bundle manifests and evals. |
|
|
@@ -115,6 +117,8 @@ The package requires Node `>=22`, and GitHub Actions runs CI on Node 22. Keep `@
|
|
|
115
117
|
|
|
116
118
|
`.flow-agents/<slug>/` is workflow working memory. Keep plans, sidecars, evidence, and handoffs there while work is active. Promote stable outcomes into `docs/`, schemas, source, or provider records before final acceptance.
|
|
117
119
|
|
|
120
|
+
Across Kontour repos, `.kontourai/` is the simple ignored home for non-durable local/generated workspace state. It is not a durable source root. Keep durable governance, Flow config, agent source, command source, and documentation in the product-owned tracked paths where developers expect them, including `.veritas/`, `.flow/`, `.agents/`, `.claude/commands`, and `docs/`. Existing transition ignores such as `.flow-agents/`, `.kontour/`, `.surface/`, `.veritas/evidence/`, `.flow/runs/`, and `.telemetry/` remain in place during migration.
|
|
121
|
+
|
|
118
122
|
`evals/fixtures/` ownership is tracked in [Fixture Ownership](fixture-ownership.md).
|
|
119
123
|
Do not delete or add fixture directories without updating that inventory and the
|
|
120
124
|
owning eval evidence.
|
|
@@ -630,14 +630,14 @@ fi
|
|
|
630
630
|
echo ""
|
|
631
631
|
echo "--- opencode Plugin Hook Chain (end-to-end telemetry persistence) ---"
|
|
632
632
|
# Execute the REAL generated plugin module under node, invoke its handlers,
|
|
633
|
-
# and assert telemetry events persist inside the workspace .telemetry/ —
|
|
633
|
+
# and assert telemetry events persist inside the workspace .kontourai/telemetry/ —
|
|
634
634
|
# not the workspace PARENT. Pins three live-smoke findings (2026-06-11):
|
|
635
635
|
# 1. spawning process.execPath fails under non-node hosts (NODE_BIN guard)
|
|
636
636
|
# 2. empty stdin makes the telemetry pipeline silently skip the emit
|
|
637
637
|
# 3. TELEMETRY_DATA_DIR escaping to the workspace parent (../../.. depth bug)
|
|
638
638
|
CHAIN_WS="$TMPDIR_EVAL/plugin-chain-opencode"
|
|
639
639
|
(cd "$ROOT_DIR/dist/opencode" && bash install.sh "$CHAIN_WS" >/dev/null 2>&1) || true
|
|
640
|
-
rm -rf "$CHAIN_WS/.telemetry" "$TMPDIR_EVAL/.telemetry"
|
|
640
|
+
rm -rf "$CHAIN_WS/.kontourai/telemetry" "$CHAIN_WS/.telemetry" "$TMPDIR_EVAL/.kontourai" "$TMPDIR_EVAL/.telemetry"
|
|
641
641
|
|
|
642
642
|
if (cd "$CHAIN_WS" && node --input-type=module -e "
|
|
643
643
|
const mod = await import('./.opencode/plugins/flow-agents.js');
|
|
@@ -653,21 +653,21 @@ fi
|
|
|
653
653
|
# The telemetry emit is detached (disowned) and can take a few seconds to
|
|
654
654
|
# land; poll rather than fixed-sleep.
|
|
655
655
|
for _i in 1 2 3 4 5 6 7 8 9 10; do
|
|
656
|
-
[[ -s "$CHAIN_WS/.telemetry/full.jsonl" ]] && break
|
|
656
|
+
[[ -s "$CHAIN_WS/.kontourai/telemetry/full.jsonl" ]] && break
|
|
657
657
|
sleep 1
|
|
658
658
|
done
|
|
659
|
-
if [[ -s "$CHAIN_WS/.telemetry/full.jsonl" ]] && node -e "
|
|
660
|
-
require('fs').readFileSync('$CHAIN_WS/.telemetry/full.jsonl','utf8').trim().split('\n').map(JSON.parse);
|
|
659
|
+
if [[ -s "$CHAIN_WS/.kontourai/telemetry/full.jsonl" ]] && node -e "
|
|
660
|
+
require('fs').readFileSync('$CHAIN_WS/.kontourai/telemetry/full.jsonl','utf8').trim().split('\n').map(JSON.parse);
|
|
661
661
|
" 2>/dev/null; then
|
|
662
|
-
_pass "opencode plugin: handlers persisted telemetry events in workspace .telemetry/"
|
|
662
|
+
_pass "opencode plugin: handlers persisted telemetry events in workspace .kontourai/telemetry/"
|
|
663
663
|
else
|
|
664
|
-
_fail "opencode plugin: no telemetry events persisted in workspace .telemetry/"
|
|
664
|
+
_fail "opencode plugin: no telemetry events persisted in workspace .kontourai/telemetry/"
|
|
665
665
|
fi
|
|
666
666
|
|
|
667
|
-
if [[ ! -e "$TMPDIR_EVAL/.telemetry" ]]; then
|
|
667
|
+
if [[ ! -e "$TMPDIR_EVAL/.kontourai/telemetry" && ! -e "$TMPDIR_EVAL/.telemetry" ]]; then
|
|
668
668
|
_pass "opencode plugin: telemetry did not leak into the workspace parent directory"
|
|
669
669
|
else
|
|
670
|
-
_fail "opencode plugin: telemetry leaked into workspace parent
|
|
670
|
+
_fail "opencode plugin: telemetry leaked into workspace parent"
|
|
671
671
|
fi
|
|
672
672
|
|
|
673
673
|
echo ""
|
|
@@ -790,10 +790,21 @@ echo "=== AC3.1 — Surface unavailable fail-closed ==="
|
|
|
790
790
|
echo ""
|
|
791
791
|
echo "--- AC3.1a: Isolated (no @kontourai/surface) with high-impact claim → BLOCKS ---"
|
|
792
792
|
|
|
793
|
+
# The gate imports the shared scripts/lib/command-log-chain.js helpers. A real
|
|
794
|
+
# install rsyncs the whole tree, so the lib always sits beside the hooks. Mirror that:
|
|
795
|
+
# the isolated gates live at "$TMP/surface-iso*/stop-goal-fit.js", so "../lib" resolves
|
|
796
|
+
# to "$TMP/lib" for both. This keeps the test exercising surface-unavailable fail-closed
|
|
797
|
+
# (not a spurious module-not-found crash).
|
|
798
|
+
ISO_LIBDIR="$TMP/lib"
|
|
799
|
+
mkdir -p "$ISO_LIBDIR"
|
|
800
|
+
cp "$ROOT/scripts/lib/command-log-chain.js" "$ISO_LIBDIR/"
|
|
801
|
+
|
|
793
802
|
# Create isolated node context that can't find @kontourai/surface
|
|
794
803
|
ISO_DIR="$TMP/surface-iso"
|
|
795
804
|
mkdir -p "$ISO_DIR/repo/.flow-agents/surftest"
|
|
805
|
+
mkdir -p "$ISO_DIR/lib"
|
|
796
806
|
cp "$GATE" "$ISO_DIR/stop-goal-fit.js"
|
|
807
|
+
cp "$ROOT/scripts/hooks/lib/local-artifact-paths.js" "$ISO_DIR/lib/"
|
|
797
808
|
printf '# Repo\n' > "$ISO_DIR/repo/AGENTS.md"
|
|
798
809
|
# Non-terminal session (execution phase, in_progress status)
|
|
799
810
|
printf '%s' '{"schema_version":"1.0","task_slug":"surftest","status":"in_progress","phase":"execution","updated_at":"2026-06-27T00:00:00Z","next_action":{"status":"in_progress","summary":"running"}}' \
|
|
@@ -837,7 +848,9 @@ echo "--- AC3.1b: Low-impact-only bundle with unavailable surface → NOT blocke
|
|
|
837
848
|
|
|
838
849
|
ISO2_DIR="$TMP/surface-iso2"
|
|
839
850
|
mkdir -p "$ISO2_DIR/repo/.flow-agents/lowtest"
|
|
851
|
+
mkdir -p "$ISO2_DIR/lib"
|
|
840
852
|
cp "$GATE" "$ISO2_DIR/stop-goal-fit.js"
|
|
853
|
+
cp "$ROOT/scripts/hooks/lib/local-artifact-paths.js" "$ISO2_DIR/lib/"
|
|
841
854
|
printf '# Repo\n' > "$ISO2_DIR/repo/AGENTS.md"
|
|
842
855
|
printf '%s' '{"schema_version":"1.0","task_slug":"lowtest","status":"in_progress","phase":"execution","updated_at":"2026-06-27T00:00:00Z","next_action":{"status":"in_progress","summary":"running"}}' \
|
|
843
856
|
> "$ISO2_DIR/repo/.flow-agents/lowtest/state.json"
|
|
@@ -1014,13 +1027,19 @@ else
|
|
|
1014
1027
|
fi
|
|
1015
1028
|
|
|
1016
1029
|
echo ""
|
|
1017
|
-
echo "--- AC3.3c:
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1030
|
+
echo "--- AC3.3c: genesis is single-sourced and imported by writer + verifier (cannot diverge) ---"
|
|
1031
|
+
# Stronger than the old "two literals match" check: the genesis literal now lives in
|
|
1032
|
+
# exactly ONE module, and both the writer and verifier import it — so divergence is
|
|
1033
|
+
# structurally impossible rather than merely currently-equal.
|
|
1034
|
+
genesis_lib=$(grep "const CHAIN_GENESIS = " "$ROOT/scripts/lib/command-log-chain.js" | sed "s/.*= '//;s/'.*//")
|
|
1035
|
+
if [ -n "$genesis_lib" ] \
|
|
1036
|
+
&& grep -q "require.*command-log-chain" "$ROOT/scripts/hooks/evidence-capture.js" \
|
|
1037
|
+
&& grep -q "require.*command-log-chain" "$ROOT/scripts/hooks/stop-goal-fit.js" \
|
|
1038
|
+
&& ! grep -qE "const CHAIN_GENESIS = '" "$ROOT/scripts/hooks/evidence-capture.js" \
|
|
1039
|
+
&& ! grep -qE "const CHAIN_GENESIS_VERIFY = '" "$ROOT/scripts/hooks/stop-goal-fit.js"; then
|
|
1040
|
+
_pass "AC3.3: genesis single-sourced in scripts/lib/command-log-chain.js ($genesis_lib); writer + verifier import it, no divergent literal"
|
|
1022
1041
|
else
|
|
1023
|
-
_fail "AC3.3:
|
|
1042
|
+
_fail "AC3.3: genesis not single-sourced (lib='$genesis_lib') or a divergent literal remains in a consumer"
|
|
1024
1043
|
fi
|
|
1025
1044
|
|
|
1026
1045
|
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -uo pipefail
|
|
3
|
+
|
|
4
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
5
|
+
TMPDIR_EVAL="$(mktemp -d)"
|
|
6
|
+
errors=0
|
|
7
|
+
|
|
8
|
+
cleanup() { rm -rf "$TMPDIR_EVAL"; }
|
|
9
|
+
trap cleanup EXIT
|
|
10
|
+
|
|
11
|
+
_pass() { echo " ✓ $1"; }
|
|
12
|
+
_fail() { echo " ✗ $1"; errors=$((errors + 1)); }
|
|
13
|
+
|
|
14
|
+
REPO="$TMPDIR_EVAL/repo"
|
|
15
|
+
mkdir -p \
|
|
16
|
+
"$REPO/.telemetry" \
|
|
17
|
+
"$REPO/.flow/runs/run-1" \
|
|
18
|
+
"$REPO/.surface/runs/run-2" \
|
|
19
|
+
"$REPO/.veritas/evidence" \
|
|
20
|
+
"$REPO/.veritas/repo-standards" \
|
|
21
|
+
"$REPO/.flow/definitions" \
|
|
22
|
+
"$REPO/.flow-agents/task"
|
|
23
|
+
|
|
24
|
+
printf '%s\n' '{"event":1}' > "$REPO/.telemetry/full.jsonl"
|
|
25
|
+
printf '%s\n' '{"run":1}' > "$REPO/.flow/runs/run-1/state.json"
|
|
26
|
+
printf '%s\n' '{"surface":1}' > "$REPO/.surface/runs/run-2/latest.json"
|
|
27
|
+
printf '%s\n' '{"evidence":1}' > "$REPO/.veritas/evidence/e.json"
|
|
28
|
+
printf '%s\n' '{"durable":1}' > "$REPO/.veritas/repo-map.json"
|
|
29
|
+
printf '%s\n' '{"standard":1}' > "$REPO/.veritas/repo-standards/std.json"
|
|
30
|
+
printf '%s\n' '{"definition":1}' > "$REPO/.flow/definitions/main.json"
|
|
31
|
+
printf '%s\n' '{"state":1}' > "$REPO/.flow-agents/task/state.json"
|
|
32
|
+
ln -s "$REPO/.telemetry/full.jsonl" "$REPO/.telemetry/link.jsonl"
|
|
33
|
+
|
|
34
|
+
if node "$ROOT/scripts/migrate-local-artifacts.mjs" --repo "$REPO" --json > "$TMPDIR_EVAL/dry.json"; then
|
|
35
|
+
if [[ ! -e "$REPO/.kontourai/telemetry/full.jsonl" ]] \
|
|
36
|
+
&& node - "$TMPDIR_EVAL/dry.json" <<'NODE'
|
|
37
|
+
const fs = require("node:fs");
|
|
38
|
+
const data = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
|
39
|
+
if (!data.summary.dry_run || data.summary.applied) throw new Error("expected dry-run");
|
|
40
|
+
const text = JSON.stringify(data);
|
|
41
|
+
for (const needle of [".telemetry/full.jsonl", ".flow/runs/run-1/state.json", ".surface/runs/run-2/latest.json", ".veritas/evidence/e.json"]) {
|
|
42
|
+
if (!text.includes(needle)) throw new Error(`missing copy plan ${needle}`);
|
|
43
|
+
}
|
|
44
|
+
for (const needle of [".veritas/repo-map.json", ".veritas/repo-standards", ".flow/definitions", ".flow-agents"]) {
|
|
45
|
+
if (!text.includes(needle)) throw new Error(`missing durable skip ${needle}`);
|
|
46
|
+
}
|
|
47
|
+
if (!text.includes("source-is-symlink")) throw new Error("missing symlink skip");
|
|
48
|
+
NODE
|
|
49
|
+
then
|
|
50
|
+
_pass "migration script defaults to dry-run and reports generated copies plus durable skips"
|
|
51
|
+
else
|
|
52
|
+
_fail "migration dry-run output was wrong"
|
|
53
|
+
fi
|
|
54
|
+
else
|
|
55
|
+
_fail "migration dry-run command failed"
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
if node "$ROOT/scripts/migrate-local-artifacts.mjs" --repo "$REPO" --apply --include-flow-agents > "$TMPDIR_EVAL/apply.out"; then
|
|
59
|
+
if [[ -f "$REPO/.kontourai/telemetry/full.jsonl" ]] \
|
|
60
|
+
&& [[ -f "$REPO/.kontourai/flow/runs/run-1/state.json" ]] \
|
|
61
|
+
&& [[ -f "$REPO/.kontourai/surface/runs/run-2/latest.json" ]] \
|
|
62
|
+
&& [[ -f "$REPO/.kontourai/veritas/evidence/e.json" ]] \
|
|
63
|
+
&& [[ -f "$REPO/.kontourai/flow-agents/task/state.json" ]] \
|
|
64
|
+
&& [[ -f "$REPO/.telemetry/full.jsonl" ]] \
|
|
65
|
+
&& [[ -f "$REPO/.flow-agents/task/state.json" ]] \
|
|
66
|
+
&& [[ ! -e "$REPO/.kontourai/veritas/repo-map.json" ]] \
|
|
67
|
+
&& [[ ! -e "$REPO/.kontourai/flow/definitions/main.json" ]]; then
|
|
68
|
+
_pass "migration apply copies only generated allowlisted paths without deleting old files"
|
|
69
|
+
else
|
|
70
|
+
_fail "migration apply copied wrong files or deleted old files"
|
|
71
|
+
fi
|
|
72
|
+
else
|
|
73
|
+
_fail "migration apply command failed"
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
TARGET_SYMLINK_WORKSPACE="$TMPDIR_EVAL/target-symlink-workspace"
|
|
77
|
+
TARGET_SYMLINK_REPO="$TARGET_SYMLINK_WORKSPACE/repo"
|
|
78
|
+
OUTSIDE="$TMPDIR_EVAL/outside.txt"
|
|
79
|
+
mkdir -p "$TARGET_SYMLINK_REPO/.telemetry" "$TARGET_SYMLINK_REPO/.kontourai/telemetry"
|
|
80
|
+
printf '%s\n' '{"safe":true}' > "$TARGET_SYMLINK_REPO/.telemetry/full.jsonl"
|
|
81
|
+
printf '%s\n' 'outside-original' > "$OUTSIDE"
|
|
82
|
+
ln -s "$OUTSIDE" "$TARGET_SYMLINK_REPO/.kontourai/telemetry/full.jsonl"
|
|
83
|
+
|
|
84
|
+
if node "$ROOT/scripts/migrate-local-artifacts.mjs" --repo "$TARGET_SYMLINK_REPO" --apply --force --json > "$TMPDIR_EVAL/target-symlink.json"; then
|
|
85
|
+
if [[ "$(cat "$OUTSIDE")" == "outside-original" ]] \
|
|
86
|
+
&& node - "$TMPDIR_EVAL/target-symlink.json" <<'NODE'
|
|
87
|
+
const fs = require("node:fs");
|
|
88
|
+
const data = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
|
89
|
+
const text = JSON.stringify(data);
|
|
90
|
+
if (!text.includes("target-is-symlink")) throw new Error("missing target symlink skip");
|
|
91
|
+
if (data.summary.copies !== 0) throw new Error(`expected no copies, got ${data.summary.copies}`);
|
|
92
|
+
NODE
|
|
93
|
+
then
|
|
94
|
+
_pass "migration apply --force refuses target symlinks"
|
|
95
|
+
else
|
|
96
|
+
_fail "migration apply --force followed or failed to report target symlink"
|
|
97
|
+
fi
|
|
98
|
+
else
|
|
99
|
+
_fail "migration apply --force target symlink command failed"
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
exit "$errors"
|
|
@@ -37,6 +37,40 @@ VALIDATOR="validate-workflow-artifacts"
|
|
|
37
37
|
ARTIFACT_DIR="$TMPDIR_EVAL/repo/.flow-agents/auto-sidecars"
|
|
38
38
|
mkdir -p "$ARTIFACT_DIR"
|
|
39
39
|
|
|
40
|
+
DEFAULT_ROOT_REPO="$TMPDIR_EVAL/default-root-repo"
|
|
41
|
+
mkdir -p "$DEFAULT_ROOT_REPO"
|
|
42
|
+
if (cd "$DEFAULT_ROOT_REPO" && flow_agents_node "$WRITER" ensure-session \
|
|
43
|
+
--task-slug default-root \
|
|
44
|
+
--title "Default Root" \
|
|
45
|
+
--summary "Default root should use the Kontour runtime artifact home." \
|
|
46
|
+
--criterion "Default root exists" \
|
|
47
|
+
--timestamp "2026-05-09T00:00:00Z" >"$TMPDIR_EVAL/default-root.out" 2>"$TMPDIR_EVAL/default-root.err"); then
|
|
48
|
+
if [[ -f "$DEFAULT_ROOT_REPO/.kontourai/flow-agents/default-root/state.json" ]] \
|
|
49
|
+
&& [[ -f "$DEFAULT_ROOT_REPO/.kontourai/flow-agents/current.json" ]] \
|
|
50
|
+
&& [[ ! -e "$DEFAULT_ROOT_REPO/.flow-agents/default-root/state.json" ]]; then
|
|
51
|
+
_pass "sidecar writer defaults new sessions to .kontourai/flow-agents"
|
|
52
|
+
else
|
|
53
|
+
_fail "sidecar writer default root did not use .kontourai/flow-agents"
|
|
54
|
+
fi
|
|
55
|
+
else
|
|
56
|
+
_fail "sidecar writer default-root ensure-session failed: $(cat "$TMPDIR_EVAL/default-root.out" "$TMPDIR_EVAL/default-root.err")"
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
OLD_ROOT_REPO="$TMPDIR_EVAL/old-root-repo"
|
|
60
|
+
mkdir -p "$OLD_ROOT_REPO/.flow-agents/old-session"
|
|
61
|
+
cat > "$OLD_ROOT_REPO/.flow-agents/current.json" <<'JSON'
|
|
62
|
+
{"schema_version":"1.0","active_slug":"old-session","artifact_dir":"old-session"}
|
|
63
|
+
JSON
|
|
64
|
+
cat > "$OLD_ROOT_REPO/.flow-agents/old-session/state.json" <<'JSON'
|
|
65
|
+
{"schema_version":"1.0","task_slug":"old-session","status":"planned","phase":"planning","next_action":{"status":"continue","summary":"continue"}}
|
|
66
|
+
JSON
|
|
67
|
+
if (cd "$OLD_ROOT_REPO" && flow_agents_node "$WRITER" current --format slug >"$TMPDIR_EVAL/old-current.out" 2>"$TMPDIR_EVAL/old-current.err") \
|
|
68
|
+
&& [[ "$(cat "$TMPDIR_EVAL/old-current.out")" == "old-session" ]]; then
|
|
69
|
+
_pass "sidecar writer reads legacy .flow-agents current session when no new root exists"
|
|
70
|
+
else
|
|
71
|
+
_fail "sidecar writer did not read legacy current session: $(cat "$TMPDIR_EVAL/old-current.out" "$TMPDIR_EVAL/old-current.err")"
|
|
72
|
+
fi
|
|
73
|
+
|
|
40
74
|
SESSION_ROOT="$TMPDIR_EVAL/repo/.flow-agents"
|
|
41
75
|
if flow_agents_node "$WRITER" ensure-session \
|
|
42
76
|
--artifact-root "$SESSION_ROOT" \
|
package/evals/run.sh
CHANGED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# test_unit_helpers.sh — Layer 1: TypeScript-layer unit tests for the PURE
|
|
3
|
+
# workflow-sidecar helpers (ops#22).
|
|
4
|
+
#
|
|
5
|
+
# Historically every assertion covering workflow-sidecar was black-box bash driving
|
|
6
|
+
# the CLI. These node:test units exercise the pure projection/validation helpers
|
|
7
|
+
# directly against the built JS — fast, deterministic, isolated — and complement
|
|
8
|
+
# (do not replace) the bash evals.
|
|
9
|
+
set -uo pipefail
|
|
10
|
+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
11
|
+
cd "$ROOT_DIR"
|
|
12
|
+
|
|
13
|
+
echo "── TS pure-helper unit tests (node --test) ──"
|
|
14
|
+
|
|
15
|
+
# The units import the build output. CI builds before evals; build here too when run
|
|
16
|
+
# standalone so the test is self-sufficient.
|
|
17
|
+
if [[ ! -f build/src/cli/workflow-sidecar.js ]]; then
|
|
18
|
+
npm run build --silent
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
if node --test src/cli/*.test.mjs; then
|
|
22
|
+
echo " PASS: workflow-sidecar pure-helper unit tests"
|
|
23
|
+
else
|
|
24
|
+
echo " FAIL: workflow-sidecar pure-helper unit tests"
|
|
25
|
+
exit 1
|
|
26
|
+
fi
|
|
@@ -41,6 +41,9 @@
|
|
|
41
41
|
"conformance:self": "node ../../packaging/conformance/run-conformance.js --self",
|
|
42
42
|
"conformance": "node ../../packaging/conformance/run-conformance.js --adapter-cmd \"node bin/conformance-shim.mjs\" --level L2"
|
|
43
43
|
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@kontourai/console-telemetry": "^0.1.0"
|
|
46
|
+
},
|
|
44
47
|
"peerDependencies": {
|
|
45
48
|
"@strands-agents/sdk": ">=1.0.0"
|
|
46
49
|
},
|