@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
|
@@ -3,11 +3,13 @@ import { fileURLToPath } from "node:url";
|
|
|
3
3
|
import * as os from "node:os";
|
|
4
4
|
import * as path from "node:path";
|
|
5
5
|
import { parseArgs, flagBool, flagList, flagString } from "../lib/args.js";
|
|
6
|
+
import { defaultArtifactRootForRead, defaultTelemetryDirForRead, defaultTelemetryDirsForRead, telemetryDataDir, flowAgentsArtifactRoot, legacyFlowAgentsArtifactRoot } from "../lib/local-artifact-root.js";
|
|
6
7
|
|
|
7
8
|
const VALID_RESULTS = new Set(["success", "partial", "failure", "not_verified"]);
|
|
8
9
|
|
|
9
10
|
function telemetryDir(flags: Record<string, string | boolean | string[]>): string {
|
|
10
|
-
|
|
11
|
+
const explicit = flagString(flags, "telemetry-dir") ?? process.env.TELEMETRY_DATA_DIR;
|
|
12
|
+
return explicit ? path.resolve(explicit) : telemetryDataDir();
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
function ensureSafeDir(dir: string): void {
|
|
@@ -110,7 +112,8 @@ function normalize(input: Record<string, unknown>[], runtime: string, flags: Rec
|
|
|
110
112
|
function importTelemetry(argv: string[], defaultRuntime?: string): number {
|
|
111
113
|
const { flags } = parseArgs(argv);
|
|
112
114
|
const runtime = defaultRuntime ?? flagString(flags, "runtime", "codex") ?? "codex";
|
|
113
|
-
const
|
|
115
|
+
const explicitInputDir = flagString(flags, "input-telemetry-dir");
|
|
116
|
+
const input = flagString(flags, "input-full-jsonl") ?? path.join(explicitInputDir ? path.resolve(explicitInputDir) : defaultTelemetryDirForRead(), "full.jsonl");
|
|
114
117
|
if (!input || !fs.existsSync(input)) throw new Error(`input telemetry file does not exist: ${input}`);
|
|
115
118
|
const dir = telemetryDir(flags);
|
|
116
119
|
ensureSafeDir(dir);
|
|
@@ -143,7 +146,7 @@ function syncArtifacts(argv: string[]): number {
|
|
|
143
146
|
const dir = telemetryDir(flags);
|
|
144
147
|
ensureSafeDir(dir);
|
|
145
148
|
const artifacts = flagList(flags, "artifact-dir");
|
|
146
|
-
const records = (artifacts.length ? artifacts : [
|
|
149
|
+
const records = (artifacts.length ? artifacts : [flowAgentsArtifactRoot(), legacyFlowAgentsArtifactRoot()]).flatMap((item) => fs.existsSync(item) ? artifactOutcomes(item, flags) : []);
|
|
147
150
|
writeJsonlUpsert(path.join(dir, "outcomes.jsonl"), records, "outcome_id");
|
|
148
151
|
if (!flagBool(flags, "quiet")) console.log(`synced ${records.length} artifact outcome(s) to ${path.join(dir, "outcomes.jsonl")}`);
|
|
149
152
|
return 0;
|
|
@@ -288,7 +291,9 @@ function markdownReport(data: Record<string, unknown>, groupBy?: string): string
|
|
|
288
291
|
|
|
289
292
|
function report(argv: string[]): number {
|
|
290
293
|
const { flags } = parseArgs(argv);
|
|
291
|
-
const
|
|
294
|
+
const requestedDirs = flagList(flags, "telemetry-dir");
|
|
295
|
+
const dirs = (requestedDirs.length ? requestedDirs.map((dir) => path.resolve(dir)) : defaultTelemetryDirsForRead()).filter(Boolean);
|
|
296
|
+
if (dirs.length === 0) dirs.push(telemetryDataDir());
|
|
292
297
|
dirs.forEach(ensureSafeDir);
|
|
293
298
|
const data = reportData(dirs, flagString(flags, "group-by"));
|
|
294
299
|
const format = flagString(flags, "format", "markdown");
|
|
@@ -319,7 +324,7 @@ function registerProject(argv: string[]): number {
|
|
|
319
324
|
ensureSafeDir(globalDir);
|
|
320
325
|
const repoRoot = path.resolve(flagString(flags, "repo-root", ".") ?? ".");
|
|
321
326
|
const name = flagString(flags, "name", path.basename(repoRoot)) ?? path.basename(repoRoot);
|
|
322
|
-
const record = { name, repo_root: repoRoot, artifact_dir:
|
|
327
|
+
const record = { name, repo_root: repoRoot, artifact_dir: defaultArtifactRootForRead(repoRoot), input_telemetry_dir: defaultTelemetryDirForRead(repoRoot), runtime: flagString(flags, "runtime", "codex"), repo: flagString(flags, "repo", name), agent: flagString(flags, "agent"), profile_id: flagString(flags, "profile-id"), prompt_id: flagString(flags, "prompt-id"), prompt_variant: flagString(flags, "prompt-variant"), skill_ids: flagList(flags, "skill-id"), skill_variant: flagString(flags, "skill-variant") };
|
|
323
328
|
const registryFile = path.join(globalDir, "projects.json");
|
|
324
329
|
const existing = fs.existsSync(registryFile) ? JSON.parse(fs.readFileSync(registryFile, "utf8")) : { projects: [] };
|
|
325
330
|
const projects = Array.isArray(existing) ? existing : Array.isArray(existing.projects) ? existing.projects : [];
|
|
@@ -342,7 +347,9 @@ function syncProject(project: Record<string, unknown>, globalDir: string): void
|
|
|
342
347
|
const name = String(project.name ?? project.repo ?? "project").replace(/[^a-zA-Z0-9_.-]+/g, "-") || "project";
|
|
343
348
|
const store = path.join(globalDir, "projects", name);
|
|
344
349
|
ensureSafeDir(store);
|
|
345
|
-
const
|
|
350
|
+
const repoRoot = String(project.repo_root ?? process.cwd());
|
|
351
|
+
const configuredArtifactDir = typeof project.artifact_dir === "string" ? project.artifact_dir : "";
|
|
352
|
+
const artifactDir = configuredArtifactDir && fs.existsSync(configuredArtifactDir) ? configuredArtifactDir : defaultArtifactRootForRead(repoRoot);
|
|
346
353
|
const flags: Record<string, string | boolean | string[]> = {
|
|
347
354
|
"repo": String(project.repo ?? name),
|
|
348
355
|
"runtime": String(project.runtime ?? "codex"),
|
|
@@ -361,9 +368,9 @@ function syncProject(project: Record<string, unknown>, globalDir: string): void
|
|
|
361
368
|
function discoverProjects(root: string): Record<string, unknown>[] {
|
|
362
369
|
if (!fs.existsSync(root)) return [];
|
|
363
370
|
const candidates = [root, ...fs.readdirSync(root).map((name) => path.join(root, name))];
|
|
364
|
-
return candidates.filter((candidate) => fs.existsSync(
|
|
371
|
+
return candidates.filter((candidate) => fs.existsSync(flowAgentsArtifactRoot(candidate)) || fs.existsSync(legacyFlowAgentsArtifactRoot(candidate))).map((repoRoot) => {
|
|
365
372
|
const name = path.basename(repoRoot);
|
|
366
|
-
return { name, repo: name, repo_root: repoRoot, artifact_dir:
|
|
373
|
+
return { name, repo: name, repo_root: repoRoot, artifact_dir: defaultArtifactRootForRead(repoRoot), input_telemetry_dir: defaultTelemetryDirForRead(repoRoot), runtime: "codex", skill_ids: [] };
|
|
367
374
|
});
|
|
368
375
|
}
|
|
369
376
|
|
|
@@ -2,6 +2,7 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { flagBool, flagString, parseArgs } from "../lib/args.js";
|
|
5
|
+
import { defaultArtifactRootForRead } from "../lib/local-artifact-root.js";
|
|
5
6
|
|
|
6
7
|
type Classification =
|
|
7
8
|
| "active_wip"
|
|
@@ -49,7 +50,7 @@ function printHelp(): void {
|
|
|
49
50
|
console.log("Read-only dry-run audit for local workflow artifact directories.");
|
|
50
51
|
console.log("");
|
|
51
52
|
console.log("Options:");
|
|
52
|
-
console.log(" --artifact-root <path> Local artifact root to scan (default: .flow-agents)");
|
|
53
|
+
console.log(" --artifact-root <path> Local artifact root to scan (default: .kontourai/flow-agents, with .flow-agents read fallback)");
|
|
53
54
|
console.log(" --json Print stable JSON buckets instead of text");
|
|
54
55
|
console.log(" --help Show this help");
|
|
55
56
|
console.log("");
|
|
@@ -269,7 +270,8 @@ export function main(argv = process.argv.slice(2)): number {
|
|
|
269
270
|
}
|
|
270
271
|
let result: AuditResult;
|
|
271
272
|
try {
|
|
272
|
-
|
|
273
|
+
const root = flagString(args.flags, "artifact-root") ? path.resolve(flagString(args.flags, "artifact-root")!) : defaultArtifactRootForRead();
|
|
274
|
+
result = audit(root);
|
|
273
275
|
} catch (error) {
|
|
274
276
|
console.error(`workflow-artifact-cleanup-audit: ${error instanceof Error ? error.message : String(error)}`);
|
|
275
277
|
return 1;
|
|
@@ -7,6 +7,7 @@ import { createRequire } from "node:module";
|
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
// ADR 0016 Abstraction A: shared FlowDefinition resolver (P-a)
|
|
9
9
|
import { resolveActiveFlowStep, resolveFlowFilePath, resolvePhaseMap, resolveRouteBackPolicy, type ActiveFlowStep } from "../lib/flow-resolver.js";
|
|
10
|
+
import { defaultArtifactRootForRead, flowAgentsArtifactRoot } from "../lib/local-artifact-root.js";
|
|
10
11
|
|
|
11
12
|
type AnyObj = Record<string, any>;
|
|
12
13
|
|
|
@@ -652,7 +653,7 @@ function isUnderDir(dir: string, root: string): boolean {
|
|
|
652
653
|
function explicitArtifactRoot(p: ReturnType<typeof parseArgs>): string {
|
|
653
654
|
const explicit = opt(p, "artifact-dir");
|
|
654
655
|
const configuredRoot = opt(p, "artifact-root");
|
|
655
|
-
if (!explicit) return path.resolve(configuredRoot
|
|
656
|
+
if (!explicit) return configuredRoot ? path.resolve(configuredRoot) : flowAgentsArtifactRoot();
|
|
656
657
|
const dir = path.resolve(explicit);
|
|
657
658
|
if (!fs.existsSync(dir)) die(`artifact directory does not exist: ${dir}`);
|
|
658
659
|
if (fs.lstatSync(dir).isSymbolicLink()) die(`artifact directory must not be a symlink: ${dir}`);
|
|
@@ -879,7 +880,7 @@ function initSidecars(dir: string, slug: string, sourceRequest: string, summary:
|
|
|
879
880
|
}
|
|
880
881
|
|
|
881
882
|
function ensureSession(p: ReturnType<typeof parseArgs>): number {
|
|
882
|
-
const root =
|
|
883
|
+
const root = opt(p, "artifact-root") ? path.resolve(opt(p, "artifact-root")) : flowAgentsArtifactRoot();
|
|
883
884
|
const slug = opt(p, "task-slug") || (opt(p, "work-item") ? workItemSlug(opt(p, "work-item")) : die("--task-slug is required (or pass --work-item to derive it)"));
|
|
884
885
|
const dir = sessionDirFor(root, slug);
|
|
885
886
|
fs.mkdirSync(dir, { recursive: true });
|
|
@@ -912,7 +913,7 @@ function ensureSession(p: ReturnType<typeof parseArgs>): number {
|
|
|
912
913
|
}
|
|
913
914
|
|
|
914
915
|
function current(p: ReturnType<typeof parseArgs>): number {
|
|
915
|
-
const root =
|
|
916
|
+
const root = opt(p, "artifact-root") ? path.resolve(opt(p, "artifact-root")) : defaultArtifactRootForRead();
|
|
916
917
|
const dir = currentDir(root);
|
|
917
918
|
if (!dir) die("no current workflow session is recorded");
|
|
918
919
|
const format = opt(p, "format", "path");
|
|
@@ -1829,7 +1830,7 @@ function assertExistingLearningValid(dir: string): void {
|
|
|
1829
1830
|
}
|
|
1830
1831
|
}
|
|
1831
1832
|
async function dogfoodPass(p: ReturnType<typeof parseArgs>): Promise<number> {
|
|
1832
|
-
const root =
|
|
1833
|
+
const root = opt(p, "artifact-root") ? path.resolve(opt(p, "artifact-root")) : defaultArtifactRootForRead();
|
|
1833
1834
|
const dir = path.resolve(opt(p, "artifact-dir") || currentDir(root) || "");
|
|
1834
1835
|
requireArtifactDirUnderRoot(dir, root);
|
|
1835
1836
|
assertExistingLearningValid(dir);
|
|
@@ -2265,7 +2266,7 @@ function loadSurfacePanelJs(): string {
|
|
|
2265
2266
|
}
|
|
2266
2267
|
|
|
2267
2268
|
async function renderTrustPanel(p: ReturnType<typeof parseArgs>): Promise<number> {
|
|
2268
|
-
const root =
|
|
2269
|
+
const root = opt(p, "artifact-root") ? path.resolve(opt(p, "artifact-root")) : defaultArtifactRootForRead();
|
|
2269
2270
|
const dir = p.positional[0] ? artifactDirFrom(p.positional[0]) : currentDir(root);
|
|
2270
2271
|
if (!dir) die("render-trust-panel requires a workflow dir or a recorded current session");
|
|
2271
2272
|
let bundle: AnyObj | null = null;
|
|
@@ -2431,7 +2432,7 @@ function livenessLifecycle(taskDir: string, slug: string, kind: "claim" | "heart
|
|
|
2431
2432
|
}
|
|
2432
2433
|
|
|
2433
2434
|
async function liveness(p: ReturnType<typeof parseArgs>): Promise<number> {
|
|
2434
|
-
const root =
|
|
2435
|
+
const root = opt(p, "artifact-root") ? path.resolve(opt(p, "artifact-root")) : flowAgentsArtifactRoot();
|
|
2435
2436
|
const action = p.positional[0] || "";
|
|
2436
2437
|
const subjectId = p.positional[1] || "";
|
|
2437
2438
|
const actor = opt(p, "actor", process.env.FLOW_AGENTS_ACTOR || "unknown");
|
|
@@ -2481,130 +2482,13 @@ async function liveness(p: ReturnType<typeof parseArgs>): Promise<number> {
|
|
|
2481
2482
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
2482
2483
|
|
|
2483
2484
|
// ─── Claim Lookup — pure helper (promotable to Surface #171) ─────────────────
|
|
2484
|
-
// buildClaimExplanation
|
|
2485
|
-
//
|
|
2486
|
-
//
|
|
2487
|
-
|
|
2488
|
-
export
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
execution: { runner: string; label: string; isError: boolean; exitCode: number | null } | null;
|
|
2492
|
-
passing: boolean;
|
|
2493
|
-
summary: string;
|
|
2494
|
-
}
|
|
2495
|
-
|
|
2496
|
-
export interface ClaimExplanation {
|
|
2497
|
-
found: boolean;
|
|
2498
|
-
status: string;
|
|
2499
|
-
value: string;
|
|
2500
|
-
claimType: string;
|
|
2501
|
-
evidence: ClaimEvidenceItem[];
|
|
2502
|
-
policy: {
|
|
2503
|
-
id: string;
|
|
2504
|
-
requiredEvidence: string[];
|
|
2505
|
-
requiredMethods?: string[];
|
|
2506
|
-
acceptanceCriteria: string[];
|
|
2507
|
-
reviewAuthority: string;
|
|
2508
|
-
} | null;
|
|
2509
|
-
why: {
|
|
2510
|
-
directInputs: AnyObj[];
|
|
2511
|
-
leafClaims: AnyObj[];
|
|
2512
|
-
diagnostics: AnyObj[];
|
|
2513
|
-
transparencyGaps: AnyObj[];
|
|
2514
|
-
changeRecords: AnyObj[];
|
|
2515
|
-
};
|
|
2516
|
-
}
|
|
2517
|
-
|
|
2518
|
-
/**
|
|
2519
|
-
* Build a structured explanation for a specific claim.
|
|
2520
|
-
* PURE: report + bundle + id in, structured explanation out.
|
|
2521
|
-
* No fs, no CLI, no .flow-agents paths. Promotable to Surface #171.
|
|
2522
|
-
*
|
|
2523
|
-
* @param report TrustReport from buildTrustReport(bundle) — required for derived status
|
|
2524
|
-
* @param bundle Raw parsed trust.bundle (BundleFile shape)
|
|
2525
|
-
* @param claimId The claim id to explain
|
|
2526
|
-
*/
|
|
2527
|
-
export function buildClaimExplanation(
|
|
2528
|
-
report: Record<string, unknown>,
|
|
2529
|
-
bundle: Record<string, unknown>,
|
|
2530
|
-
claimId: string,
|
|
2531
|
-
): ClaimExplanation {
|
|
2532
|
-
const reportClaims = Array.isArray(report.claims) ? (report.claims as AnyObj[]) : [];
|
|
2533
|
-
const reportClaim = reportClaims.find((c: AnyObj) => c.id === claimId);
|
|
2534
|
-
|
|
2535
|
-
if (!reportClaim) {
|
|
2536
|
-
return {
|
|
2537
|
-
found: false,
|
|
2538
|
-
status: "unknown",
|
|
2539
|
-
value: "",
|
|
2540
|
-
claimType: "",
|
|
2541
|
-
evidence: [],
|
|
2542
|
-
policy: null,
|
|
2543
|
-
why: { directInputs: [], leafClaims: [], diagnostics: [], transparencyGaps: [], changeRecords: [] },
|
|
2544
|
-
};
|
|
2545
|
-
}
|
|
2546
|
-
|
|
2547
|
-
const bundleClaims = Array.isArray(bundle.claims) ? (bundle.claims as AnyObj[]) : [];
|
|
2548
|
-
const bundleClaim = bundleClaims.find((c: AnyObj) => c.id === claimId) ?? reportClaim;
|
|
2549
|
-
const bundlePolicies = Array.isArray(bundle.policies) ? (bundle.policies as AnyObj[]) : [];
|
|
2550
|
-
const bundleEvidence = Array.isArray(bundle.evidence) ? (bundle.evidence as AnyObj[]) : [];
|
|
2551
|
-
|
|
2552
|
-
// Governing policy — follow verificationPolicyId into bundle.policies[]
|
|
2553
|
-
const verificationPolicyId = typeof bundleClaim.verificationPolicyId === "string" ? bundleClaim.verificationPolicyId : undefined;
|
|
2554
|
-
const rawPolicy = verificationPolicyId ? bundlePolicies.find((p: AnyObj) => p.id === verificationPolicyId) : undefined;
|
|
2555
|
-
const policy = rawPolicy
|
|
2556
|
-
? {
|
|
2557
|
-
id: String(rawPolicy.id ?? ""),
|
|
2558
|
-
requiredEvidence: Array.isArray(rawPolicy.requiredEvidence) ? (rawPolicy.requiredEvidence as string[]) : [],
|
|
2559
|
-
requiredMethods: Array.isArray(rawPolicy.requiredMethods) ? (rawPolicy.requiredMethods as string[]) : undefined,
|
|
2560
|
-
acceptanceCriteria: Array.isArray(rawPolicy.acceptanceCriteria) ? (rawPolicy.acceptanceCriteria as string[]) : [],
|
|
2561
|
-
reviewAuthority: String(rawPolicy.reviewAuthority ?? ""),
|
|
2562
|
-
}
|
|
2563
|
-
: null;
|
|
2564
|
-
|
|
2565
|
-
// Evidence enhancement: pull evidence items for this claim, surface the execution block
|
|
2566
|
-
const claimEvidenceItems = bundleEvidence.filter((ev: AnyObj) => ev && ev.claimId === claimId);
|
|
2567
|
-
const evidence: ClaimEvidenceItem[] = claimEvidenceItems.map((ev: AnyObj) => {
|
|
2568
|
-
const exec = ev.execution && typeof ev.execution === "object" ? (ev.execution as AnyObj) : null;
|
|
2569
|
-
const execution = exec
|
|
2570
|
-
? {
|
|
2571
|
-
runner: String(exec.runner ?? exec.label ?? ""),
|
|
2572
|
-
label: String(exec.label ?? exec.runner ?? ""),
|
|
2573
|
-
isError: Boolean(exec.isError ?? (typeof exec.exitCode === "number" && exec.exitCode !== 0)),
|
|
2574
|
-
exitCode: typeof exec.exitCode === "number" ? exec.exitCode : null,
|
|
2575
|
-
}
|
|
2576
|
-
: null;
|
|
2577
|
-
return {
|
|
2578
|
-
evidenceType: String(ev.evidenceType ?? ev.type ?? "unknown"),
|
|
2579
|
-
label: String(ev.label ?? ev.excerptOrSummary ?? ev.sourceRef ?? ev.id ?? ""),
|
|
2580
|
-
execution,
|
|
2581
|
-
passing: execution ? !execution.isError : String(ev.status ?? "") !== "disputed",
|
|
2582
|
-
summary: String(ev.excerptOrSummary ?? ev.summary ?? ev.label ?? ""),
|
|
2583
|
-
};
|
|
2584
|
-
});
|
|
2585
|
-
|
|
2586
|
-
// Drilldown: extract from report structure (report.transparencyGaps, report.changeRecords)
|
|
2587
|
-
const allGaps = Array.isArray(report.transparencyGaps) ? (report.transparencyGaps as AnyObj[]) : [];
|
|
2588
|
-
const allChanges = Array.isArray(report.changeRecords) ? (report.changeRecords as AnyObj[]) : [];
|
|
2589
|
-
const transparencyGaps = allGaps.filter((g: AnyObj) => g && g.claimId === claimId);
|
|
2590
|
-
const changeRecords = allChanges.filter((c: AnyObj) => c && c.claimId === claimId);
|
|
2591
|
-
|
|
2592
|
-
return {
|
|
2593
|
-
found: true,
|
|
2594
|
-
status: String(reportClaim.status ?? "unknown"),
|
|
2595
|
-
value: String(bundleClaim.value ?? reportClaim.value ?? ""),
|
|
2596
|
-
claimType: String(bundleClaim.claimType ?? reportClaim.claimType ?? ""),
|
|
2597
|
-
evidence,
|
|
2598
|
-
policy,
|
|
2599
|
-
why: {
|
|
2600
|
-
directInputs: [], // populated by buildDerivationDrilldown if non-leaf
|
|
2601
|
-
leafClaims: [],
|
|
2602
|
-
diagnostics: [],
|
|
2603
|
-
transparencyGaps,
|
|
2604
|
-
changeRecords,
|
|
2605
|
-
},
|
|
2606
|
-
};
|
|
2607
|
-
}
|
|
2485
|
+
// buildClaimExplanation + its types are extracted to ./sidecar-claim-explain.ts
|
|
2486
|
+
// (ops#22): a PURE projection (report + bundle + id in, structured explanation out)
|
|
2487
|
+
// with no fs/CLI/shared state, unit-tested in isolation. Re-exported here so the
|
|
2488
|
+
// library facade (src/index.ts) and the IO `claimLookup` handler below are unchanged.
|
|
2489
|
+
export { buildClaimExplanation } from "./sidecar-claim-explain.js";
|
|
2490
|
+
export type { ClaimEvidenceItem, ClaimExplanation } from "./sidecar-claim-explain.js";
|
|
2491
|
+
import { buildClaimExplanation } from "./sidecar-claim-explain.js";
|
|
2608
2492
|
|
|
2609
2493
|
/**
|
|
2610
2494
|
* claim <id> <dir>
|
|
@@ -2768,7 +2652,9 @@ Available claim ids:
|
|
|
2768
2652
|
async function main(): Promise<number> {
|
|
2769
2653
|
const p = parseArgs(process.argv.slice(2));
|
|
2770
2654
|
if (!p.command) die("workflow-sidecar command is required");
|
|
2771
|
-
const lockRoot = ["ensure-session", "current", "dogfood-pass", "liveness"].includes(p.command)
|
|
2655
|
+
const lockRoot = ["ensure-session", "current", "dogfood-pass", "liveness"].includes(p.command)
|
|
2656
|
+
? (opt(p, "artifact-root") ? path.resolve(opt(p, "artifact-root")) : (p.command === "ensure-session" ? flowAgentsArtifactRoot() : defaultArtifactRootForRead()))
|
|
2657
|
+
: p.command === "record-agent-event" ? explicitArtifactRoot(p) : p.command === "claim" ? (p.positional[1] ? path.resolve(p.positional[1]) : "") : p.positional[0] ? artifactDirFrom(p.positional[0]) : "";
|
|
2772
2658
|
return withLock(lockRoot, ["ensure-session", "record-agent-event", "dogfood-pass"].includes(p.command), p.command, () => {
|
|
2773
2659
|
switch (p.command) {
|
|
2774
2660
|
case "ensure-session": return ensureSession(p);
|
package/src/index.ts
CHANGED
|
@@ -16,6 +16,20 @@
|
|
|
16
16
|
import * as path from "node:path";
|
|
17
17
|
import { loadJson as _loadJson, writeJson as _writeJson } from "./cli/workflow-sidecar.js";
|
|
18
18
|
|
|
19
|
+
export {
|
|
20
|
+
defaultArtifactRootForRead,
|
|
21
|
+
defaultTelemetryDirForRead,
|
|
22
|
+
defaultTelemetryDirsForRead,
|
|
23
|
+
firstExistingPath,
|
|
24
|
+
flowAgentsArtifactRoot,
|
|
25
|
+
KONTOURAI_DIR,
|
|
26
|
+
legacyFlowAgentsArtifactRoot,
|
|
27
|
+
LEGACY_FLOW_AGENTS_DIR,
|
|
28
|
+
legacyTelemetryDataDir,
|
|
29
|
+
LEGACY_TELEMETRY_DIR,
|
|
30
|
+
telemetryDataDir,
|
|
31
|
+
} from "./lib/local-artifact-root.js";
|
|
32
|
+
|
|
19
33
|
export {
|
|
20
34
|
// Trust-bundle (Hachure) validation — the same validator the writer uses.
|
|
21
35
|
validateTrustBundle,
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
|
|
4
|
+
export const KONTOURAI_DIR = ".kontourai";
|
|
5
|
+
export const LEGACY_FLOW_AGENTS_DIR = ".flow-agents";
|
|
6
|
+
export const LEGACY_TELEMETRY_DIR = ".telemetry";
|
|
7
|
+
|
|
8
|
+
export function flowAgentsArtifactRoot(cwd = process.cwd()): string {
|
|
9
|
+
return path.resolve(cwd, KONTOURAI_DIR, "flow-agents");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function legacyFlowAgentsArtifactRoot(cwd = process.cwd()): string {
|
|
13
|
+
return path.resolve(cwd, LEGACY_FLOW_AGENTS_DIR);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function telemetryDataDir(cwd = process.cwd()): string {
|
|
17
|
+
return path.resolve(cwd, KONTOURAI_DIR, "telemetry");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function legacyTelemetryDataDir(cwd = process.cwd()): string {
|
|
21
|
+
return path.resolve(cwd, LEGACY_TELEMETRY_DIR);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function firstExistingPath(candidates: string[]): string {
|
|
25
|
+
return candidates.find((candidate) => fs.existsSync(candidate)) ?? candidates[0];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function defaultArtifactRootForRead(cwd = process.cwd()): string {
|
|
29
|
+
return firstExistingPath([flowAgentsArtifactRoot(cwd), legacyFlowAgentsArtifactRoot(cwd)]);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function defaultTelemetryDirForRead(cwd = process.cwd()): string {
|
|
33
|
+
return firstExistingPath([telemetryDataDir(cwd), legacyTelemetryDataDir(cwd)]);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function defaultTelemetryDirsForRead(cwd = process.cwd()): string[] {
|
|
37
|
+
const dirs = [telemetryDataDir(cwd), legacyTelemetryDataDir(cwd)];
|
|
38
|
+
return dirs.filter((dir, index) => dirs.indexOf(dir) === index && fs.existsSync(dir));
|
|
39
|
+
}
|
|
@@ -74,6 +74,7 @@ const hookFilePolicies = new Map<string, { category: string; requiredNeedles: st
|
|
|
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
|
]);
|