@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,381 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
//
|
|
3
|
+
// Shared bundle-shape classification/divergence-construction, extracted from
|
|
4
|
+
// scripts/ci/trust-reconcile.js so a local, pre-push preflight (issue #356) can reuse
|
|
5
|
+
// EXACTLY the same shape checks CI enforces, rather than risk a forked copy that
|
|
6
|
+
// silently drifts from what trust-reconcile.js actually does (the historical failure
|
|
7
|
+
// mode this module exists to close off — see command-log-chain.js for the identical
|
|
8
|
+
// rationale applied to the hash-chain/laundering primitives).
|
|
9
|
+
//
|
|
10
|
+
// This module is SHAPE-only: it classifies a bundle's own claims/evidence and builds
|
|
11
|
+
// the `issues[]` entries that do not require a fresh CI command re-run (no `runCommand`,
|
|
12
|
+
// no manifest command execution). The ACTUAL fresh-run comparison for reconcilable
|
|
13
|
+
// command claims (`ciResult.passed`) stays in trust-reconcile.js, since that requires a
|
|
14
|
+
// live CI/local command execution a local preflight must not perform.
|
|
15
|
+
//
|
|
16
|
+
// trust-reconcile.js requires this module instead of defining these functions inline —
|
|
17
|
+
// see its own comments at the require() site and the (former) location of
|
|
18
|
+
// classifyBundleClaims for the extraction history.
|
|
19
|
+
|
|
20
|
+
// hasLaunderingOperator is imported (not re-implemented) so this module and
|
|
21
|
+
// scripts/ci/trust-reconcile.js apply the identical exit-code-mask heuristic.
|
|
22
|
+
const { hasLaunderingOperator } = require('./command-log-chain.js');
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Classify a trust.bundle's claims into: reconcilable command claims (test_output +
|
|
26
|
+
* execution.label), session-local claims (attestation/observation/citation), never-captured
|
|
27
|
+
* or unbacked command claims (not-run divergence), and command-backed claims carrying a
|
|
28
|
+
* waiver (waiver-on-command divergence). Returns
|
|
29
|
+
* { reconcilable, sessionLocal, noEvidenceCommand, waiverOnCommand }.
|
|
30
|
+
*
|
|
31
|
+
* Source of truth: evidence[].execution.label is the command string recorded at capture time.
|
|
32
|
+
* evidence[].passing (normalized) means the agent claimed this passed. `claim.status` is NOT
|
|
33
|
+
* trusted here — the caller re-derives it CI-side (see derive-claim-status.mjs / finding-3).
|
|
34
|
+
*
|
|
35
|
+
* WS8 iteration-2 hardening:
|
|
36
|
+
* - finding 1: ANY pass-asserting claim whose evidence is `evidenceType: test_output`
|
|
37
|
+
* (Surface's default when unset) but which did NOT reconcile — i.e. it has no
|
|
38
|
+
* manifest-matchable execution.label — is a divergence, NOT session-local. A test_output
|
|
39
|
+
* claim either reconciles against the manifest or is a divergence; it is never accepted on
|
|
40
|
+
* self-reported status. (Previously only the literal claimType `workflow.check.command`
|
|
41
|
+
* was guarded, so a fabricated kind:"test" claim with no command slipped through.)
|
|
42
|
+
* - finding 4: a command-backed (test_output) claim carrying a waiver is a divergence — a
|
|
43
|
+
* command-backed check reconciles against CI or fails; it cannot be waived.
|
|
44
|
+
*/
|
|
45
|
+
function classifyBundleClaims(bundle) {
|
|
46
|
+
const evidence = Array.isArray(bundle.evidence) ? bundle.evidence : [];
|
|
47
|
+
const claims = Array.isArray(bundle.claims) ? bundle.claims : [];
|
|
48
|
+
|
|
49
|
+
const claimById = new Map();
|
|
50
|
+
for (const c of claims) if (c && c.id) claimById.set(c.id, c);
|
|
51
|
+
|
|
52
|
+
// Evidence indexing. A missing evidenceType defaults to test_output for backward
|
|
53
|
+
// compatibility with pre-classification bundles (same default classifyEvidence uses).
|
|
54
|
+
const claimHasLabeledTestOutput = new Set(); // test_output evidence WITH an execution.label
|
|
55
|
+
const claimHasTestOutputEvidence = new Set(); // ANY test_output evidence (label or not)
|
|
56
|
+
// WS8 iteration-4 (converged finding): the session-local (non-test_output) evidenceType per
|
|
57
|
+
// claim, so the reconciler can name it on the loud ATTESTED marker below — a fabricated
|
|
58
|
+
// human_attestation/attestation/external claim with no --command is otherwise
|
|
59
|
+
// indistinguishable, in the reconciler's own output, from a genuinely re-runnable check.
|
|
60
|
+
const claimEvidenceType = new Map();
|
|
61
|
+
for (const ev of evidence) {
|
|
62
|
+
if (!ev || !ev.claimId) continue;
|
|
63
|
+
const evType = ev.evidenceType || 'test_output';
|
|
64
|
+
if (evType !== 'test_output') {
|
|
65
|
+
if (!claimEvidenceType.has(ev.claimId)) claimEvidenceType.set(ev.claimId, evType);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
claimHasTestOutputEvidence.add(ev.claimId);
|
|
69
|
+
if (ev.execution && ev.execution.label) claimHasLabeledTestOutput.add(ev.claimId);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// finding 4: a command-backed (test_output-evidence) claim that also carries a waiver.
|
|
73
|
+
const waiverOnCommand = [];
|
|
74
|
+
for (const c of claims) {
|
|
75
|
+
if (!c || !c.id) continue;
|
|
76
|
+
const waiver = (c.metadata && typeof c.metadata === 'object') ? c.metadata.waiver : undefined;
|
|
77
|
+
if (waiver && typeof waiver === 'object' && claimHasTestOutputEvidence.has(c.id)) {
|
|
78
|
+
waiverOnCommand.push({ claimId: c.id, claimType: String(c.claimType || ''), subject: c.subjectId || c.fieldOrBehavior || c.id });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// (A) Reconcilable claimed-passes: evidence items that are test_output (CI-reconcilable),
|
|
83
|
+
// carry an execution.label, and assert pass. Session-local evidenceTypes
|
|
84
|
+
// (crawl_observation, human_attestation, attestation, policy_rule, source_excerpt,
|
|
85
|
+
// document_citation, calculation_trace) are NOT reconciled per-command — they are handled
|
|
86
|
+
// by the session-local/waiver path below.
|
|
87
|
+
const reconcilable = [];
|
|
88
|
+
const reconcilableClaimIds = new Set();
|
|
89
|
+
const seen = new Set();
|
|
90
|
+
for (const ev of evidence) {
|
|
91
|
+
if (!ev || !ev.execution || !ev.execution.label) continue;
|
|
92
|
+
if (!isPassingValue(ev.passing)) continue;
|
|
93
|
+
const evType = ev.evidenceType || 'test_output';
|
|
94
|
+
if (evType !== 'test_output') continue; // session-local — not CI-reconcilable
|
|
95
|
+
const cmd = normalizeCmd(ev.execution.label);
|
|
96
|
+
if (!cmd) continue;
|
|
97
|
+
reconcilableClaimIds.add(ev.claimId);
|
|
98
|
+
if (seen.has(cmd)) continue;
|
|
99
|
+
seen.add(cmd);
|
|
100
|
+
const claim = claimById.get(ev.claimId);
|
|
101
|
+
reconcilable.push({ cmd, claimId: ev.claimId, evId: ev.id, claimType: claim ? String(claim.claimType || '') : '' });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// (B) Session-local claims, never-captured command claims, and unreconciled test_output.
|
|
105
|
+
const sessionLocal = [];
|
|
106
|
+
const noEvidenceCommand = [];
|
|
107
|
+
const seenClaims = new Set();
|
|
108
|
+
for (const c of claims) {
|
|
109
|
+
if (!c || !c.id || typeof c.claimType !== 'string') continue;
|
|
110
|
+
// #267/#282: a superseded critique write is HISTORY — excluded from reconcile evaluation so a
|
|
111
|
+
// resolved session converges (a fail critique that a later same-reviewer pass superseded no
|
|
112
|
+
// longer blocks). Scoped to NON-test_output claims so a command-backed claim can never launder
|
|
113
|
+
// a real failure by carrying superseded_by — a test_output claim always reconciles or diverges.
|
|
114
|
+
if (c.metadata && typeof c.metadata === 'object' && c.metadata.superseded_by && !claimHasTestOutputEvidence.has(c.id)) continue;
|
|
115
|
+
if (reconcilableClaimIds.has(c.id)) continue; // handled by (A)
|
|
116
|
+
if (seenClaims.has(c.id)) continue;
|
|
117
|
+
const status = String(c.status || '');
|
|
118
|
+
const assertsPass = isPassingValue(c.value) || status === 'verified' || status === 'assumed';
|
|
119
|
+
const isFailing = status === 'disputed' || status === 'rejected';
|
|
120
|
+
if (!assertsPass && !isFailing) continue; // pending/unknown non-asserting — ignore (as before)
|
|
121
|
+
seenClaims.add(c.id);
|
|
122
|
+
|
|
123
|
+
// finding 1: a pass-asserting claim backed by test_output evidence that did NOT reconcile
|
|
124
|
+
// (it has test_output evidence but no manifest-matchable execution.label — otherwise it
|
|
125
|
+
// would be in bucket A) is a not-run divergence. A test_output claim reconciles against the
|
|
126
|
+
// manifest or it is a divergence — it is NEVER accepted as session-local on self-report.
|
|
127
|
+
if (assertsPass && claimHasTestOutputEvidence.has(c.id)) {
|
|
128
|
+
const rawCmd = normalizeCmd(c.fieldOrBehavior || c.value || '');
|
|
129
|
+
noEvidenceCommand.push({ cmd: rawCmd || `[claim:${c.id}]`, claimId: c.id, claimType: c.claimType, reason: 'test_output-unreconciled' });
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// A workflow.check.command claim with no captured (labeled) evidence is a never-captured
|
|
134
|
+
// claimed pass — not-run divergence (anti-gaming teeth preserved).
|
|
135
|
+
if (assertsPass && c.claimType === 'workflow.check.command' && !claimHasLabeledTestOutput.has(c.id)) {
|
|
136
|
+
const rawCmd = normalizeCmd(c.fieldOrBehavior || c.value || '');
|
|
137
|
+
noEvidenceCommand.push({ cmd: rawCmd || `[claim:${c.id}:${c.claimType}]`, claimId: c.id, claimType: c.claimType, reason: 'no-evidence-command' });
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const waiver = (c.metadata && typeof c.metadata === 'object') ? c.metadata.waiver : undefined;
|
|
142
|
+
sessionLocal.push({
|
|
143
|
+
claimId: c.id,
|
|
144
|
+
claimType: c.claimType,
|
|
145
|
+
assertedStatus: status,
|
|
146
|
+
value: c.value,
|
|
147
|
+
waiver: (waiver && typeof waiver === 'object') ? waiver : null,
|
|
148
|
+
subject: c.subjectId || c.fieldOrBehavior || c.id,
|
|
149
|
+
evidenceType: claimEvidenceType.get(c.id) || 'unknown',
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return { reconcilable, sessionLocal, noEvidenceCommand, waiverOnCommand };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Normalize a command string: collapse whitespace, trim. (Mirrors trust-reconcile.js's own.) */
|
|
157
|
+
function normalizeCmd(cmd) {
|
|
158
|
+
return String(cmd || '').replace(/\s+/g, ' ').trim();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Normalize ev.passing to a boolean.
|
|
163
|
+
* Treats true / 1 / "true" / "pass" as passing.
|
|
164
|
+
* Prevents a claim from dodging reconciliation via a non-boolean value.
|
|
165
|
+
*/
|
|
166
|
+
function isPassingValue(v) {
|
|
167
|
+
return v === true || v === 1 || v === 'true' || v === 'pass';
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ---------------------------------------------------------------------------
|
|
171
|
+
// Pure shape-level divergence ("issue") construction.
|
|
172
|
+
//
|
|
173
|
+
// Each function below takes already-computed classification inputs (the buckets
|
|
174
|
+
// classifyBundleClaims returns, plus a resolved manifest / derived-status map) and
|
|
175
|
+
// returns an issues[] array structurally IDENTICAL in shape (`{ type, cmd?, message }`)
|
|
176
|
+
// to what trust-reconcile.js's Step 2 block pushes inline. None of these functions
|
|
177
|
+
// execute a command or otherwise perform a fresh CI run — they are pure, local, and fast.
|
|
178
|
+
// ---------------------------------------------------------------------------
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* finding 4 (server-side): a command-backed (test_output-evidence) claim carrying a
|
|
182
|
+
* waiver is a divergence — a command-backed check reconciles against CI or fails; it
|
|
183
|
+
* cannot be waived away.
|
|
184
|
+
*/
|
|
185
|
+
function waiverOnCommandIssues(waiverOnCommand) {
|
|
186
|
+
const issues = [];
|
|
187
|
+
for (const { claimId, claimType, subject } of waiverOnCommand || []) {
|
|
188
|
+
issues.push({
|
|
189
|
+
type: 'waiver-on-command-check',
|
|
190
|
+
message: `trust divergence: claim '${claimId}' (${subject}, claimType: ${claimType}) carries a waiver but is backed by test_output evidence — a command-backed check reconciles against CI or fails and cannot be waived`,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
return issues;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* not-run divergences: never-captured command claims (no evidence) AND test_output
|
|
198
|
+
* claims that did not reconcile (no manifest-matchable execution.label).
|
|
199
|
+
*/
|
|
200
|
+
function noEvidenceCommandIssues(noEvidenceCommand) {
|
|
201
|
+
const issues = [];
|
|
202
|
+
for (const { cmd, claimId, claimType, reason } of noEvidenceCommand || []) {
|
|
203
|
+
const message = reason === 'test_output-unreconciled'
|
|
204
|
+
? `trust divergence: claim '${claimId}' (claimType: ${claimType}) asserts pass with test_output evidence but has no manifest-matched execution.label — a test_output claim must reconcile against the manifest or it is a divergence (never accepted as session-local)`
|
|
205
|
+
: `trust divergence: claim '${claimId}' (claimType: ${claimType}) asserts pass but has no supporting evidence item — command never captured`;
|
|
206
|
+
issues.push({ type: 'not-run', cmd, message });
|
|
207
|
+
}
|
|
208
|
+
return issues;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Manifest-membership subset of the `reconcilable` loop: ONLY the "not in the reconcile
|
|
213
|
+
* manifest" `not-run` case, plus the laundering check (via the shared hasLaunderingOperator).
|
|
214
|
+
* The ACTUAL fresh-run comparison (`ciResult.passed`) is NOT here — it requires a live CI/
|
|
215
|
+
* local command execution the preflight must not perform, and stays in trust-reconcile.js.
|
|
216
|
+
*
|
|
217
|
+
* Returns { issues, unresolved } where `unresolved` is the subset of `reconcilable` entries
|
|
218
|
+
* that passed the laundering + manifest-membership checks and therefore DO require a fresh
|
|
219
|
+
* CI run to fully reconcile — callers that need full parity (trust-reconcile.js) continue
|
|
220
|
+
* from there; callers that are shape-only (the local preflight) simply do not resolve them
|
|
221
|
+
* further and treat "manifest-matched, not laundered" as shape-clean.
|
|
222
|
+
*/
|
|
223
|
+
function reconcilableManifestIssues(reconcilable, manifestByCmd) {
|
|
224
|
+
const issues = [];
|
|
225
|
+
const unresolved = [];
|
|
226
|
+
for (const entry of reconcilable || []) {
|
|
227
|
+
const { cmd } = entry;
|
|
228
|
+
const normalCmd = normalizeCmd(cmd);
|
|
229
|
+
|
|
230
|
+
// (a) Laundering operator check — must come first (most specific signal).
|
|
231
|
+
if (hasLaunderingOperator(cmd)) {
|
|
232
|
+
issues.push({
|
|
233
|
+
type: 'laundering',
|
|
234
|
+
cmd,
|
|
235
|
+
message: `trust divergence: agent claimed '${cmd}' passed; command contains exit-code-laundering operator (|| ... / ; true / ; exit 0 / etc.)`,
|
|
236
|
+
});
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// A test_output claim MUST name a manifest (required-lane) command. An agent
|
|
241
|
+
// cannot self-label an arbitrary command test_output to dodge the manifest.
|
|
242
|
+
const manifestEntry = manifestByCmd.get(normalCmd);
|
|
243
|
+
if (!manifestEntry) {
|
|
244
|
+
issues.push({
|
|
245
|
+
type: 'not-run',
|
|
246
|
+
cmd,
|
|
247
|
+
message: `trust divergence: agent claimed '${cmd}' passed; command is not in the reconcile manifest — a test_output claim must name a manifest/required-lane command (CI cannot self-declare an arbitrary command)`,
|
|
248
|
+
});
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
unresolved.push({ ...entry, manifestEntry });
|
|
253
|
+
}
|
|
254
|
+
return { issues, unresolved };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Session-local claims: not CI-reconcilable, but NOT a pass bypass. Each must either
|
|
259
|
+
* (a) carry a loud, justified waiver, or (b) resolve a real CI-RE-DERIVED `verified`
|
|
260
|
+
* status. WS8 iteration-2 hardening:
|
|
261
|
+
* - finding 3: the status used here is RE-DERIVED CI-side, never the self-reported
|
|
262
|
+
* claim.status. A mismatch is a `status-misassertion` divergence.
|
|
263
|
+
* - finding 2: `assumed` alone is NO LONGER a silent pass. `assumed` is acceptable
|
|
264
|
+
* ONLY with a waiver (printed as a loud WAIVED line by the caller). An unwaived
|
|
265
|
+
* `assumed` claim is an `unwaived-assumed` divergence (restores pre-WS8 semantics
|
|
266
|
+
* where `assumed` alone never satisfied assertsPass).
|
|
267
|
+
*
|
|
268
|
+
* Q1/iteration-1-F1 (extraction-granularity + caller-controlled mode): `derivedStatus` is a
|
|
269
|
+
* `Map<string,string|null>|null` — the SAME value trust-reconcile.js's `deriveClaimStatuses()`
|
|
270
|
+
* produces (shells out to derive-claim-status.mjs, local-only, no CI command execution).
|
|
271
|
+
* `opts.onUnderivable` makes the `derivedStatus === null` behavior an EXPLICIT caller choice —
|
|
272
|
+
* there is no silent default that fails open:
|
|
273
|
+
* - `'fail'` (DEFAULT — the safe/original CI behavior; a caller that forgets `opts` never
|
|
274
|
+
* fails open): when `derivedStatus` is null, EVERY session-local pass-asserting claim
|
|
275
|
+
* becomes a `status-underivable` divergence (verbatim pre-#356 message + `continue`) —
|
|
276
|
+
* we never fall back to trusting the bundle's own status. `scripts/ci/trust-reconcile.js`
|
|
277
|
+
* MUST use this mode; it is CI's trust anchor.
|
|
278
|
+
* - `'reduce'` (LOCAL-PREFLIGHT-ONLY opt-in): when `derivedStatus` is null, DEGRADE to a
|
|
279
|
+
* documented reduced-coverage mode — status-misassertion/status-underivable checks are
|
|
280
|
+
* skipped entirely (nothing to re-derive against), but the waiver/unwaived-assumed/
|
|
281
|
+
* session-local-failed/unwaived-session-local checks still run against the claim's own
|
|
282
|
+
* self-reported `assertedStatus`. Only `src/cli/workflow-sidecar.ts`'s local
|
|
283
|
+
* `runReconcilePreflight` opts into this (and surfaces the reduced coverage to the user via
|
|
284
|
+
* a warning) — CI must never reach this branch.
|
|
285
|
+
* When `derivedStatus` is non-null, both modes behave identically (full parity with CI).
|
|
286
|
+
*
|
|
287
|
+
* Returns { issues, attestedCount, logEvents } — attestedCount mirrors trust-reconcile.js's
|
|
288
|
+
* own "N attested claim(s) accepted without independent verification" summary line;
|
|
289
|
+
* logEvents is the ordered list of WAIVED/ATTESTED terminal classifications (F3, iteration-1)
|
|
290
|
+
* so a caller's stdout narrative (e.g. trust-reconcile.js's WAIVED/ATTESTED log lines) is
|
|
291
|
+
* driven by this single classification instead of a parallel re-derivation.
|
|
292
|
+
*/
|
|
293
|
+
function sessionLocalShapeIssues(sessionLocal, derivedStatus, opts) {
|
|
294
|
+
const onUnderivable = (opts && opts.onUnderivable) || 'fail';
|
|
295
|
+
const issues = [];
|
|
296
|
+
let attestedCount = 0;
|
|
297
|
+
// F3 (iteration-1): single source of truth for the WAIVED/ATTESTED classification, so
|
|
298
|
+
// trust-reconcile.js's stdout narrative loop consumes this instead of re-deriving its own
|
|
299
|
+
// (previously parallel, driftable) copy. Only populated when a claim reaches the WAIVED or
|
|
300
|
+
// ATTESTED terminal below (never for issues) — callers that don't log can ignore it.
|
|
301
|
+
const logEvents = [];
|
|
302
|
+
|
|
303
|
+
for (const { claimId, claimType, assertedStatus, waiver, subject, evidenceType } of sessionLocal || []) {
|
|
304
|
+
let status;
|
|
305
|
+
if (derivedStatus) {
|
|
306
|
+
// finding 3: re-derive; never trust the asserted status.
|
|
307
|
+
const derived = derivedStatus.get(claimId);
|
|
308
|
+
if (derived === undefined || derived === null) {
|
|
309
|
+
issues.push({
|
|
310
|
+
type: 'status-underivable',
|
|
311
|
+
message: `trust divergence: session-local claim '${claimId}' (claimType: ${claimType}) could not be re-derived CI-side from the bundle's own evidence/events/policies — refusing to trust its self-reported status '${assertedStatus || 'unknown'}' (fail-closed)`,
|
|
312
|
+
});
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
if (derived !== assertedStatus) {
|
|
316
|
+
issues.push({
|
|
317
|
+
type: 'status-misassertion',
|
|
318
|
+
message: `trust divergence: session-local claim '${claimId}' (claimType: ${claimType}) asserts status '${assertedStatus || 'unknown'}' but CI re-derivation from the bundle's own evidence/events/policies yields '${derived}' — the reconciler does not trust self-reported claim.status`,
|
|
319
|
+
});
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
status = derived;
|
|
323
|
+
} else if (onUnderivable === 'reduce') {
|
|
324
|
+
// Reduced-coverage mode (derivedStatus === null, explicit local-preflight opt-in): trust
|
|
325
|
+
// the self-reported status for the remaining shape checks only. status-misassertion/
|
|
326
|
+
// status-underivable are, by definition, not checkable without a derivation source —
|
|
327
|
+
// documented gap, not a bug.
|
|
328
|
+
status = assertedStatus;
|
|
329
|
+
} else {
|
|
330
|
+
// Fail-closed mode (default; CI): restores the pre-#356 inline behavior verbatim — we
|
|
331
|
+
// never fall back to trusting a self-reported status.
|
|
332
|
+
issues.push({
|
|
333
|
+
type: 'status-underivable',
|
|
334
|
+
message: `trust divergence: session-local claim '${claimId}' (claimType: ${claimType}) asserts status '${assertedStatus || 'unknown'}' but CI-side re-derivation is unavailable — refusing to trust a self-reported status (fail-closed)`,
|
|
335
|
+
});
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (status === 'disputed' || status === 'rejected') {
|
|
340
|
+
issues.push({
|
|
341
|
+
type: 'session-local-failed',
|
|
342
|
+
message: `trust divergence: session-local claim '${claimId}' (claimType: ${claimType}) has re-derived status '${status}' — a failing/rejected claim blocks (session-local classification is not a pass bypass)`,
|
|
343
|
+
});
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
// finding 2: a waiver is the ONLY way an `assumed` (or otherwise non-`verified`)
|
|
347
|
+
// session-local claim passes. `verified` still passes on its own re-derived status.
|
|
348
|
+
if (waiver && waiver.reason && waiver.approved_by) {
|
|
349
|
+
logEvents.push({ kind: 'waived', claimId, claimType, subject, evidenceType, status, waiver });
|
|
350
|
+
continue; // WAIVED — caller may log this loudly; not an issue.
|
|
351
|
+
}
|
|
352
|
+
if (status === 'verified') {
|
|
353
|
+
attestedCount++;
|
|
354
|
+
logEvents.push({ kind: 'attested', claimId, claimType, subject, evidenceType, status });
|
|
355
|
+
continue; // ATTESTED (not independently verifiable at L0) — caller may log; not an issue.
|
|
356
|
+
}
|
|
357
|
+
if (status === 'assumed') {
|
|
358
|
+
issues.push({
|
|
359
|
+
type: 'unwaived-assumed',
|
|
360
|
+
message: `trust divergence: session-local claim '${claimId}' (claimType: ${claimType}) has re-derived status 'assumed' but carries no waiver — 'assumed' alone is not a pass; it requires a documented waiver (--accepted-gap-reason/--waived-by) to be accepted`,
|
|
361
|
+
});
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
issues.push({
|
|
365
|
+
type: 'unwaived-session-local',
|
|
366
|
+
message: `trust divergence: session-local claim '${claimId}' (claimType: ${claimType}) asserts pass with re-derived status '${status || 'unknown'}' but has no waiver and no CI-re-derived verified status`,
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return { issues, attestedCount, logEvents };
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
module.exports = {
|
|
374
|
+
classifyBundleClaims,
|
|
375
|
+
normalizeCmd,
|
|
376
|
+
isPassingValue,
|
|
377
|
+
waiverOnCommandIssues,
|
|
378
|
+
noEvidenceCommandIssues,
|
|
379
|
+
reconcilableManifestIssues,
|
|
380
|
+
sessionLocalShapeIssues,
|
|
381
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# relay.sh — OPTIONAL console liveness relay (#295, ADR 0021 §4/§7).
|
|
3
|
+
#
|
|
4
|
+
# Mirrors a single liveness event (claim/heartbeat/release) to the hosted Console as a
|
|
5
|
+
# `kontour.console.liveness` record, reusing the telemetry transport's shared `console_post_json`
|
|
6
|
+
# (endpoint-allow gate, Bearer + tenant auth, timeouts, temp-file handling, detached fire — one core,
|
|
7
|
+
# never forked). The Console side ingests this record type and projects the fleet view + runs the
|
|
8
|
+
# janitor (console repo #125); this script is only the flow-agents EMIT half.
|
|
9
|
+
#
|
|
10
|
+
# STRICTLY OPTIONAL and local-first (ADR 0012 §5): a no-op unless FLOW_AGENTS_CONSOLE_LIVENESS_RELAY
|
|
11
|
+
# is enabled AND a console endpoint is configured. Best-effort throughout — it must NEVER block, slow,
|
|
12
|
+
# or fail the local liveness write that already happened before this was invoked. Every failure path
|
|
13
|
+
# is a quiet `exit 0`.
|
|
14
|
+
#
|
|
15
|
+
# Invoked (fully detached, best-effort) from scripts/hooks/lib/liveness-write.js after the durable
|
|
16
|
+
# local append. Usage: relay.sh '<liveness-event-json>'
|
|
17
|
+
set -uo pipefail
|
|
18
|
+
|
|
19
|
+
# Opt-in gate — off by default.
|
|
20
|
+
case "${FLOW_AGENTS_CONSOLE_LIVENESS_RELAY:-}" in
|
|
21
|
+
1 | true | TRUE | yes | on) ;;
|
|
22
|
+
*) exit 0 ;;
|
|
23
|
+
esac
|
|
24
|
+
|
|
25
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" || exit 0
|
|
26
|
+
|
|
27
|
+
# transport.sh sources ${TELEMETRY_DIR}/lib/redact.sh at load — point it at the telemetry lib dir,
|
|
28
|
+
# then reuse the SAME console POST core the telemetry mirror uses.
|
|
29
|
+
export TELEMETRY_DIR="${TELEMETRY_DIR:-$SCRIPT_DIR/../telemetry}"
|
|
30
|
+
[[ -f "$TELEMETRY_DIR/lib/transport.sh" ]] || exit 0
|
|
31
|
+
# shellcheck source=/dev/null
|
|
32
|
+
source "$TELEMETRY_DIR/lib/transport.sh" 2>/dev/null || exit 0
|
|
33
|
+
|
|
34
|
+
# Resolve the liveness records endpoint: an explicit override wins, else the base console URL + /records
|
|
35
|
+
# ("POST /records with a liveness record type"). No console configured ⇒ no-op.
|
|
36
|
+
endpoint="${FLOW_AGENTS_CONSOLE_LIVENESS_ENDPOINT_URL:-}"
|
|
37
|
+
if [[ -z "$endpoint" ]]; then
|
|
38
|
+
base="${FLOW_AGENTS_CONSOLE_URL:-${CONSOLE_TELEMETRY_URL:-${CONSOLE_URL:-}}}"
|
|
39
|
+
[[ -z "$base" ]] && exit 0
|
|
40
|
+
endpoint="${base%/}/records"
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
# Auth reuses the env names console_post_json reads (CONSOLE_TELEMETRY_TOKEN / CONSOLE_TENANT_ID),
|
|
44
|
+
# accepting FLOW_AGENTS_CONSOLE_* aliases and an optional token file. Never fatal on a missing/bad
|
|
45
|
+
# token — console_post_json only adds a header when the value passes its safety check.
|
|
46
|
+
if [[ -z "${CONSOLE_TELEMETRY_TOKEN:-}" ]]; then
|
|
47
|
+
token_file="${FLOW_AGENTS_CONSOLE_TOKEN_FILE:-${CONSOLE_TELEMETRY_TOKEN_FILE:-}}"
|
|
48
|
+
if [[ -n "$token_file" && -r "$token_file" ]]; then
|
|
49
|
+
CONSOLE_TELEMETRY_TOKEN="$(tr -d '\r\n' < "$token_file" 2>/dev/null || true)"
|
|
50
|
+
export CONSOLE_TELEMETRY_TOKEN
|
|
51
|
+
fi
|
|
52
|
+
fi
|
|
53
|
+
if [[ -z "${CONSOLE_TENANT_ID:-}" && -n "${FLOW_AGENTS_CONSOLE_TENANT:-}" ]]; then
|
|
54
|
+
export CONSOLE_TENANT_ID="$FLOW_AGENTS_CONSOLE_TENANT"
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
# The liveness event arrives as a single JSON argument.
|
|
58
|
+
event_json="${1:-}"
|
|
59
|
+
[[ -z "$event_json" ]] && exit 0
|
|
60
|
+
|
|
61
|
+
# Build the kontour.console.liveness record with jq — guarantees valid JSON and, critically,
|
|
62
|
+
# proper string escaping of every untrusted field (actor/subjectId/branch/artifact_dir), so hostile
|
|
63
|
+
# control bytes are \u-escaped rather than emitted raw (injection discipline). No jq ⇒ no-op.
|
|
64
|
+
command -v jq >/dev/null 2>&1 || exit 0
|
|
65
|
+
record="$(printf '%s' "$event_json" | jq -c '{
|
|
66
|
+
schema: "kontour.console.liveness",
|
|
67
|
+
version: "0.1",
|
|
68
|
+
type: (.type // "claim"),
|
|
69
|
+
subjectId: .subjectId,
|
|
70
|
+
actor: .actor,
|
|
71
|
+
actor_key: (.actor_key // .actorKey // null),
|
|
72
|
+
at: .at,
|
|
73
|
+
ttlSeconds: (.ttlSeconds // null),
|
|
74
|
+
host: (.host // null),
|
|
75
|
+
branch: (.branch // null),
|
|
76
|
+
artifact_dir: (.artifact_dir // .artifactDir // null)
|
|
77
|
+
}' 2>/dev/null)" || exit 0
|
|
78
|
+
[[ -z "$record" || "$record" == "null" ]] && exit 0
|
|
79
|
+
|
|
80
|
+
# Optional field redaction (default none — jq escaping already neutralizes injection), then the
|
|
81
|
+
# shared best-effort POST. console_post_json enforces the https/localhost endpoint-allow gate.
|
|
82
|
+
processed="$(redact_event "$record" "${FLOW_AGENTS_CONSOLE_LIVENESS_REDACT:-none}")"
|
|
83
|
+
console_post_json "$endpoint" "$processed"
|
|
84
|
+
exit 0
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://kontourai.dev/schemas/kontour.console.economics/0.1.json",
|
|
4
|
+
"title": "kontour.console.economics",
|
|
5
|
+
"description": "Per-run kit-economics record (flow-agents #349, console ADR 0003). Immutable per-run fact: cost, time, iterations, and defects caught. cost and defects are co-required (R7 Goodhart guard).",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": true,
|
|
8
|
+
"required": ["schema", "version", "run_id", "cost", "time", "iterations", "defects"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"schema": { "const": "kontour.console.economics" },
|
|
11
|
+
"version": { "const": "0.1" },
|
|
12
|
+
"run_id": { "type": "string" },
|
|
13
|
+
"at": { "type": ["string", "null"] },
|
|
14
|
+
"task_slug": { "type": ["string", "null"] },
|
|
15
|
+
"model": { "type": ["string", "null"] },
|
|
16
|
+
"pricing_version": { "type": ["string", "null"] },
|
|
17
|
+
"cost": {
|
|
18
|
+
"type": "object",
|
|
19
|
+
"additionalProperties": true,
|
|
20
|
+
"required": [
|
|
21
|
+
"input_tokens",
|
|
22
|
+
"output_tokens",
|
|
23
|
+
"cache_creation_input_tokens",
|
|
24
|
+
"cache_read_input_tokens",
|
|
25
|
+
"estimated_cost_usd",
|
|
26
|
+
"by_model"
|
|
27
|
+
],
|
|
28
|
+
"properties": {
|
|
29
|
+
"input_tokens": { "type": "number" },
|
|
30
|
+
"output_tokens": { "type": "number" },
|
|
31
|
+
"cache_creation_input_tokens": { "type": "number" },
|
|
32
|
+
"cache_read_input_tokens": { "type": "number" },
|
|
33
|
+
"estimated_cost_usd": { "type": "number" },
|
|
34
|
+
"by_model": { "type": "array" }
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"time": {
|
|
38
|
+
"type": "object",
|
|
39
|
+
"additionalProperties": true,
|
|
40
|
+
"required": ["wall_clock_s", "human_wait_s"],
|
|
41
|
+
"properties": {
|
|
42
|
+
"wall_clock_s": { "type": "number" },
|
|
43
|
+
"human_wait_s": { "type": "number" }
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"phases": {
|
|
47
|
+
"type": "array",
|
|
48
|
+
"items": {
|
|
49
|
+
"type": "object",
|
|
50
|
+
"additionalProperties": true,
|
|
51
|
+
"required": ["phase"],
|
|
52
|
+
"properties": {
|
|
53
|
+
"phase": { "type": "string" },
|
|
54
|
+
"input_tokens": { "type": "number" },
|
|
55
|
+
"output_tokens": { "type": "number" },
|
|
56
|
+
"cache_creation_input_tokens": { "type": "number" },
|
|
57
|
+
"cache_read_input_tokens": { "type": "number" },
|
|
58
|
+
"estimated_cost_usd": { "type": "number" },
|
|
59
|
+
"wall_clock_s": { "type": "number" }
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"iterations": {
|
|
64
|
+
"type": "object",
|
|
65
|
+
"additionalProperties": true,
|
|
66
|
+
"required": ["count", "route_backs"],
|
|
67
|
+
"properties": {
|
|
68
|
+
"count": { "type": "number" },
|
|
69
|
+
"route_backs": { "type": "number" }
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"defects": {
|
|
73
|
+
"type": "object",
|
|
74
|
+
"additionalProperties": true,
|
|
75
|
+
"required": [
|
|
76
|
+
"gate_fires",
|
|
77
|
+
"findings_by_severity",
|
|
78
|
+
"caught_false_completions",
|
|
79
|
+
"verification_verdict"
|
|
80
|
+
],
|
|
81
|
+
"properties": {
|
|
82
|
+
"gate_fires": { "type": "number" },
|
|
83
|
+
"findings_by_severity": {
|
|
84
|
+
"type": "object",
|
|
85
|
+
"additionalProperties": true,
|
|
86
|
+
"required": ["critical", "high", "medium", "low"],
|
|
87
|
+
"properties": {
|
|
88
|
+
"critical": { "type": "number" },
|
|
89
|
+
"high": { "type": "number" },
|
|
90
|
+
"medium": { "type": "number" },
|
|
91
|
+
"low": { "type": "number" }
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
"caught_false_completions": { "type": "number" },
|
|
95
|
+
"verification_verdict": {
|
|
96
|
+
"type": "string",
|
|
97
|
+
"enum": ["PASS", "FAIL", "NOT_VERIFIED"]
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
"delegations": {
|
|
102
|
+
"type": "array",
|
|
103
|
+
"description": "Per-sub-agent delegation facts (#415), one entry per delegated agent_id, joined from <slug>/agents/<agent-id>/events.jsonl (latest delegation/escalation event wins for role/model; outcome derived from the agent's full event set). No per-delegation cost (no runtime isolates per-sub-agent tokens yet — see signals.per_delegation_tokens; cost-per-(role,model) is a console projection). Empty when no --agents-dir or no delegations.",
|
|
104
|
+
"items": {
|
|
105
|
+
"type": "object",
|
|
106
|
+
"additionalProperties": true,
|
|
107
|
+
"required": ["role", "resolved_model"],
|
|
108
|
+
"properties": {
|
|
109
|
+
"agent_id": { "type": ["string", "null"] },
|
|
110
|
+
"role": { "type": "string" },
|
|
111
|
+
"resolved_model": { "type": "string" },
|
|
112
|
+
"summary": { "type": ["string", "null"] },
|
|
113
|
+
"escalated_from": { "type": "string" },
|
|
114
|
+
"dispatch_count": {
|
|
115
|
+
"type": "number",
|
|
116
|
+
"description": "How many times the orchestrator (re)dispatched this agent_id (delegation + escalation events). >1 means it was re-prompted — an orchestrator-observable rework signal that needs no sub-agent introspection."
|
|
117
|
+
},
|
|
118
|
+
"outcome": {
|
|
119
|
+
"type": "string",
|
|
120
|
+
"description": "Derived ONLY from real recorded signals, never fabricated. 'unavailable' = no terminal verdict recorded on this harness, distinct from a real accept.",
|
|
121
|
+
"enum": ["accepted", "rework", "diverged", "failed", "unavailable"]
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
"signals": {
|
|
127
|
+
"type": "object",
|
|
128
|
+
"additionalProperties": true,
|
|
129
|
+
"description": "What telemetry the current runtime actually exposed (harness-capability declaration — see docs/specs/harness-capability-matrix.md). Lets a consumer distinguish a real zero from a harness-blind gap.",
|
|
130
|
+
"properties": {
|
|
131
|
+
"runtime": { "type": ["string", "null"] },
|
|
132
|
+
"per_delegation_tokens": {
|
|
133
|
+
"type": "boolean",
|
|
134
|
+
"description": "Whether the runtime isolates per-sub-agent token usage. False on every runtime today → per-delegation cost is unavailable."
|
|
135
|
+
},
|
|
136
|
+
"per_delegation_outcome": {
|
|
137
|
+
"type": "string",
|
|
138
|
+
"enum": ["full", "partial", "none", "n/a"],
|
|
139
|
+
"description": "Coverage of the per-delegation outcome signal on this run: full=every delegation has a real outcome; partial=some; none=delegations exist but none had a verdict/escalation; n/a=no delegations observed."
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
"tenant_id": { "type": ["string", "null"] }
|
|
144
|
+
}
|
|
145
|
+
}
|