opencode-swarm 7.136.4 → 7.137.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.
Files changed (82) hide show
  1. package/dist/agents/agent-output-schema.d.ts +2 -2
  2. package/dist/cli/{config-doctor-qwp5y9dk.js → config-doctor-vyx82nx2.js} +2 -2
  3. package/dist/cli/{core-jpjk2qvt.js → core-6w5sd5bc.js} +1 -1
  4. package/dist/cli/{curation-policy-8tsbaa4q.js → curation-policy-8tvs4pa0.js} +7 -6
  5. package/dist/cli/{curator-763smew7.js → curator-43xqs75k.js} +25 -25
  6. package/dist/cli/{curator-llm-factory-dg8mmzy7.js → curator-llm-factory-8nywfdsh.js} +25 -25
  7. package/dist/cli/{evidence-summary-service-nse06dqr.js → evidence-summary-service-85gw9p2m.js} +7 -7
  8. package/dist/cli/{gate-evidence-kdygjr56.js → gate-evidence-s2kytsrj.js} +4 -4
  9. package/dist/cli/{guardrail-explain-6jc012g3.js → guardrail-explain-8kkqz8tx.js} +26 -26
  10. package/dist/cli/{guardrail-log-86aw7fne.js → guardrail-log-e2v9qw71.js} +3 -3
  11. package/dist/cli/{hive-promoter-en786ja4.js → hive-promoter-jt9ky0hn.js} +25 -25
  12. package/dist/cli/{index-45y0y3xh.js → index-1zrb3vt2.js} +7 -7
  13. package/dist/cli/{index-d2sf9an1.js → index-5r6kkhd7.js} +4 -4
  14. package/dist/cli/{index-ne28wyyc.js → index-5wjw05dy.js} +2 -2
  15. package/dist/cli/{index-06htxvzq.js → index-72wzb81c.js} +3 -3
  16. package/dist/cli/{index-7t3vjw5e.js → index-877ah4kx.js} +1 -1
  17. package/dist/cli/{index-tncy55bp.js → index-8ehyt3rx.js} +1 -1
  18. package/dist/cli/{index-gjs5g97v.js → index-91kk2e9j.js} +1 -1
  19. package/dist/cli/{index-t1s4b5ha.js → index-92v63zzr.js} +688 -637
  20. package/dist/cli/{index-rkp6jvc3.js → index-aze9n64z.js} +2 -2
  21. package/dist/cli/{index-wy2f83j5.js → index-d00q9kpq.js} +5 -5
  22. package/dist/cli/{index-qv1xc6rd.js → index-d0nhz96c.js} +2 -2
  23. package/dist/cli/{index-hh34tyv7.js → index-dxatvs3f.js} +1 -1
  24. package/dist/cli/{index-4cv991ra.js → index-f24r6yqk.js} +3 -3
  25. package/dist/cli/{index-bb3ks0v3.js → index-fgkhpdc6.js} +6 -6
  26. package/dist/cli/{index-w6j1n5az.js → index-gv00q5gc.js} +1 -1
  27. package/dist/cli/index-h5sbn1zr.js +1670 -0
  28. package/dist/cli/{index-et8b70c8.js → index-j2ghsdzz.js} +27 -27
  29. package/dist/cli/{index-89fpahtg.js → index-j6ewv2pe.js} +6 -1
  30. package/dist/cli/{index-nfm9f10v.js → index-k3mezk96.js} +1 -1
  31. package/dist/cli/{index-94pvh7hc.js → index-mg26zd8x.js} +2 -2
  32. package/dist/cli/{index-pq8ge6fe.js → index-mwdzkr6k.js} +3 -2
  33. package/dist/cli/{index-b3jfcptk.js → index-n3h6rmbe.js} +1 -1
  34. package/dist/cli/{index-fkdxfdbx.js → index-p124rnwb.js} +4 -4
  35. package/dist/cli/{index-1hef020t.js → index-pcwjz5de.js} +1 -1
  36. package/dist/cli/{index-kym0cctr.js → index-qy3rgmkc.js} +1 -1
  37. package/dist/cli/{index-d61h3f3h.js → index-s3j9fsab.js} +2 -2
  38. package/dist/cli/{index-zzhyws9g.js → index-vft1pg34.js} +1 -1
  39. package/dist/cli/{index-fhnbzz75.js → index-wg98bsxz.js} +2 -2
  40. package/dist/cli/{index-g0sdhyqk.js → index-y25nsyaq.js} +1 -1
  41. package/dist/cli/index.js +25 -25
  42. package/dist/cli/{knowledge-escalator-e815bveb.js → knowledge-escalator-9k0kbx6b.js} +8 -7
  43. package/dist/cli/{knowledge-events-dz2tyhpw.js → knowledge-events-8nz3sa2d.js} +6 -5
  44. package/dist/cli/{knowledge-link-zr40rnwr.js → knowledge-link-t94b79qe.js} +5 -4
  45. package/dist/cli/{knowledge-store-97qr7m9k.js → knowledge-store-esghe3f9.js} +6 -5
  46. package/dist/cli/{knowledge-validator-xsetvy4v.js → knowledge-validator-bjxag1jp.js} +9 -8
  47. package/dist/cli/{pending-delegations-0h5b18p7.js → pending-delegations-kshh66s2.js} +3 -3
  48. package/dist/cli/{pr-subscriptions-jn0h047q.js → pr-subscriptions-5fpzz4f3.js} +3 -3
  49. package/dist/cli/{runner-deeswadt.js → runner-d820ws7y.js} +5 -5
  50. package/dist/cli/{scan-cursor-129fwf7e.js → scan-cursor-t212v00f.js} +7 -6
  51. package/dist/cli/{schema-3xdza5gg.js → schema-11gyrec3.js} +1 -1
  52. package/dist/cli/{scope-persistence-5xc9ntdh.js → scope-persistence-sjz200dt.js} +4 -4
  53. package/dist/cli/{skill-generator-8zhprasg.js → skill-generator-a07hpmw6.js} +10 -9
  54. package/dist/cli/{telemetry-6678gya0.js → telemetry-h7h7f0ez.js} +2 -1
  55. package/dist/cli/{worktree-collision-ownership-wt7cc850.js → worktree-collision-ownership-xzwab4xt.js} +3 -3
  56. package/dist/config/constants.d.ts +1 -1
  57. package/dist/config/evidence-schema.d.ts +103 -103
  58. package/dist/config/plan-schema.d.ts +10 -10
  59. package/dist/config/schema.d.ts +8 -8
  60. package/dist/consensus/contracts.d.ts +3 -3
  61. package/dist/hooks/guardrails/file-authority.d.ts +20 -1
  62. package/dist/hooks/hive-promoter.d.ts +1 -1
  63. package/dist/index.js +320 -321
  64. package/dist/memory/index.d.ts +1 -1
  65. package/dist/memory/local-jsonl-provider.d.ts +4 -1
  66. package/dist/memory/redaction.d.ts +91 -0
  67. package/dist/memory/sqlite-provider.d.ts +5 -1
  68. package/dist/observability/catalog.d.ts +84 -0
  69. package/dist/observability/envelope.d.ts +401 -0
  70. package/dist/observability/ids.d.ts +97 -0
  71. package/dist/observability/index.d.ts +58 -0
  72. package/dist/observability/legacy.d.ts +68 -0
  73. package/dist/observability/observe.d.ts +112 -0
  74. package/dist/observability/otel-mapping.d.ts +48 -0
  75. package/dist/observability/relationships.d.ts +44 -0
  76. package/dist/observability/sampling.d.ts +77 -0
  77. package/dist/summaries/schema.d.ts +2 -2
  78. package/dist/telemetry.d.ts +4 -1
  79. package/dist/utils/arg-hash.d.ts +113 -0
  80. package/dist/utils/stable-stringify.d.ts +47 -2
  81. package/package.json +3 -2
  82. package/dist/cli/index-cze4bq1x.js +0 -358
@@ -0,0 +1,97 @@
1
+ /** Hex width of a W3C `trace-id`. */
2
+ export declare const TRACE_ID_HEX_LENGTH: number;
3
+ /** Hex width of a W3C span id. */
4
+ export declare const SPAN_ID_HEX_LENGTH: number;
5
+ /**
6
+ * Environment variable that overrides the lineage salt.
7
+ *
8
+ * Deliberately NOT read at module scope: reading `process.env` during module
9
+ * evaluation would make the salt depend on import order and would defeat any
10
+ * test that sets it after import. `resolveLineageSalt()` reads it per call.
11
+ */
12
+ export declare const LINEAGE_SALT_ENV = "SWARM_OBSERVABILITY_LINEAGE_SALT";
13
+ /**
14
+ * Salt used when {@link LINEAGE_SALT_ENV} is unset.
15
+ *
16
+ * A constant default is intentional: lineage refs must be stable for a given
17
+ * path across processes and restarts, otherwise `projectRef` could not correlate
18
+ * two runs of the same project. Operators who want cross-install unlinkability
19
+ * set {@link LINEAGE_SALT_ENV} to a private value.
20
+ */
21
+ export declare const DEFAULT_LINEAGE_SALT = "opencode-swarm/observability/lineage/v1";
22
+ /** Hex characters retained from the SHA-256 digest for a lineage ref. */
23
+ export declare const PSEUDONYMOUS_REF_LENGTH = 16;
24
+ /** A correlated `traceId` + `spanId` pair produced from one CSPRNG call. */
25
+ export interface TraceAndSpanId {
26
+ readonly traceId: string;
27
+ readonly spanId: string;
28
+ }
29
+ /**
30
+ * Generate a W3C-compatible trace id: 32 lowercase hex characters, never
31
+ * all-zero.
32
+ *
33
+ * NAME COLLISION, deliberate to record rather than rename: `newTraceId` in
34
+ * `src/hooks/knowledge-events.ts` has the identical `(): string` signature but
35
+ * a DIFFERENT on-wire shape — a 36-character RFC 4122 UUID, not 32 hex
36
+ * characters. Nothing outside this directory imports this one, and it is no
37
+ * longer re-exported from `src/observability/index.ts`, so the two cannot be
38
+ * confused through the barrel. (`newEventId` is unaffected: both return
39
+ * `randomUUID()`.)
40
+ */
41
+ export declare function newTraceId(): string;
42
+ /**
43
+ * Generate a W3C-compatible span id: 16 lowercase hex characters, never
44
+ * all-zero.
45
+ */
46
+ export declare function newSpanId(): string;
47
+ /** Generate an event id (RFC 4122 UUID v4). */
48
+ export declare function newEventId(): string;
49
+ /**
50
+ * Derive a correlated trace/span pair, consuming 24 bytes from the pool above.
51
+ *
52
+ * This exists for the hot path: `createObservation` runs on every `emit()`, and
53
+ * src/telemetry.ts:315-317 documents that path as deliberately frugal.
54
+ */
55
+ export declare function newTraceAndSpanId(): TraceAndSpanId;
56
+ /**
57
+ * Resolve the lineage salt.
58
+ *
59
+ * Reads {@link LINEAGE_SALT_ENV} at call time (never at module scope) and falls
60
+ * back to {@link DEFAULT_LINEAGE_SALT}. Reading an environment variable is not
61
+ * I/O — no file, socket, or subprocess is touched.
62
+ */
63
+ export declare function resolveLineageSalt(): string;
64
+ /**
65
+ * Produce a pseudonymous reference for an absolute path.
66
+ *
67
+ * `sha256(salt + NUL + absolutePath)`, truncated to 16 hex characters.
68
+ *
69
+ * Properties this guarantees, and why each matters (issue #2029 item 2 / AC3):
70
+ * - The result never CONTAINS or ENCODES the input path. It is a one-way
71
+ * digest, not an encoding.
72
+ * - Two different project paths carrying the SAME cohort label produce
73
+ * DIFFERENT refs, because the path is part of the digest input.
74
+ * - The result is stable for a given (salt, path) pair, so the same project
75
+ * correlates across processes and restarts.
76
+ *
77
+ * What it does NOT guarantee, stated plainly rather than overclaimed: this is a
78
+ * PSEUDONYM, not an anonymous value, and it is not "irreversible" in the sense
79
+ * that matters. With the PUBLIC {@link DEFAULT_LINEAGE_SALT} the digest input is
80
+ * fully known except for the path, and real paths are low entropy, so a holder
81
+ * of an export can CONFIRM a guessed path by re-hashing candidates and matching
82
+ * the ref. Setting {@link LINEAGE_SALT_ENV} to a private per-install value
83
+ * restores guess-resistance and makes refs unlinkable across installs. The
84
+ * algorithm is deliberately left alone here: deriving and persisting a
85
+ * per-install salt needs init-path I/O, which AGENTS.md invariant 1 forbids, so
86
+ * it belongs with #2047.
87
+ *
88
+ * SCOPE of the AC3 claim: `cohortRef` and `worktreeRef` are computed only when a
89
+ * caller supplies `cohortLabel` / `worktreeId` to `initObservability`. The sole
90
+ * production caller (`src/index.ts:732-744`) supplies neither, so both are
91
+ * `undefined` in every real run today; AC3 is asserted at unit level against
92
+ * this API, not against a production emission path.
93
+ *
94
+ * @param absolutePath - Value to pseudonymize. Never stored or echoed.
95
+ * @param salt - Salt from {@link resolveLineageSalt}.
96
+ */
97
+ export declare function pseudonymousRef(absolutePath: string, salt: string): string;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * The observability event contract (issue #2029).
3
+ *
4
+ * Reference documentation: `docs/observability-event-contract.md`.
5
+ *
6
+ * ## What this module is
7
+ *
8
+ * A versioned, discriminated, catalogued envelope for every observability record
9
+ * this plugin produces, plus the adapter that projects the existing untyped
10
+ * telemetry payloads onto it. The defect class it closes: *a production
11
+ * telemetry record is written without a versioned, discriminated, catalogued
12
+ * envelope — so a consumer cannot determine producer, schema generation, or
13
+ * unknown-versus-absent, and a new event kind can enter the stream without any
14
+ * registration.*
15
+ *
16
+ * ## The guarantees callers can rely on
17
+ *
18
+ * - **Zero I/O.** No filesystem, network, subprocess, dynamic import, or
19
+ * OpenTelemetry SDK anywhere in this directory. `node:crypto` and `zod` are
20
+ * the only imports. That is what keeps `initObservability` safe on the plugin
21
+ * init path (AGENTS.md invariant 1) and the bundle Node-ESM-portable
22
+ * (invariant 2).
23
+ * - **`createObservation` never throws** — not on a circular object, a
24
+ * function/`Symbol`/`BigInt` payload, an unknown kind, or `null` data. It
25
+ * never serializes, clones, or deep-traverses the payload.
26
+ * - **`legacy.raw` is an ALIAS** to the caller's payload, never a copy. Key
27
+ * order, key collisions and `undefined`-key elision in the written line depend
28
+ * on it.
29
+ * - **Nothing is synthesized.** An identifier the producer does not hold stays
30
+ * `undefined`; an unversioned store records `sourceSchemaVersion: null`
31
+ * (unknown, not zero); an absent field is listed in `legacy.unknown` rather
32
+ * than defaulted.
33
+ * - **Validation returns, never throws.** `validateEventRelationships` and
34
+ * `assertBoundedCardinality` both yield verdicts.
35
+ *
36
+ * ## What it is NOT, stated plainly
37
+ *
38
+ * The envelope is not yet authoritative. `toLegacyTelemetryLine` is a
39
+ * deliberately lossy, legacy-pinned projection: the non-legacy fields
40
+ * (`eventId`, `trace`, `lineage`, `provenance`, `policy`, `writerSequence`,
41
+ * `relationshipViolations`) are currently discarded, and their consumer lands in
42
+ * **#2047**. Consequently `validateEventRelationships` does not bite at runtime
43
+ * today — it bites in unit tests and in the static contract check.
44
+ */
45
+ export type { CatalogEntry, OtelMappingKind } from './catalog.js';
46
+ export { CATALOG_KINDS, EVENT_CATALOG, getCatalogEntry, } from './catalog.js';
47
+ export type { Lineage, ObservabilityEvent, Outcome, Policy, Provenance, } from './envelope.js';
48
+ export { OBSERVABILITY_SCHEMA_VERSION, ObservabilityEventSchema, } from './envelope.js';
49
+ export type { TraceAndSpanId } from './ids.js';
50
+ export { newEventId, newSpanId, pseudonymousRef, SPAN_ID_HEX_LENGTH, TRACE_ID_HEX_LENGTH, } from './ids.js';
51
+ export { adaptLegacyTelemetryPayload, extractOutcome, extractWorkflowIds, KNOWN_TELEMETRY_KEYS, LEGACY_ADAPTER_RULES, NON_OBJECT_PAYLOAD_MARKER, } from './legacy.js';
52
+ export type { InitObservabilityInput } from './observe.js';
53
+ export { createObservation, initObservability, resetObservabilityForTesting, toLegacyTelemetryLine, } from './observe.js';
54
+ export { mappingForEntry, OPENINFERENCE_ATTRIBUTES, OPENINFERENCE_MAPPING_VERSION, OTEL_GENAI_ATTRIBUTES, OTEL_GENAI_MAPPING_VERSION, } from './otel-mapping.js';
55
+ export type { RelationshipValidationResult } from './relationships.js';
56
+ export { RELATIONSHIP_VIOLATION_CODES, validateEventRelationships, } from './relationships.js';
57
+ export type { CardinalityResult } from './sampling.js';
58
+ export { assertBoundedCardinality, METRIC_LABEL_ALLOWLIST, shouldSample, } from './sampling.js';
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Legacy telemetry adapter (issue #2029 item 4 / AC2).
3
+ *
4
+ * Its real production input is the untyped `data: Record<string, unknown>` that
5
+ * `src/telemetry.ts:emit()` receives from 40+ call sites: caller-ordered,
6
+ * unversioned, with extra and missing fields. That is legacy-shaped input by
7
+ * definition, and adapting it is this module's genuine job.
8
+ *
9
+ * Two hard constraints govern every function here:
10
+ * 1. **Never throw.** `emit()` documents a never-throw guarantee
11
+ * (`src/telemetry.ts:266,294`) and `src/telemetry.test.ts:137-162`
12
+ * exercises circular objects, functions, `Symbol`s and `BigInt`s.
13
+ * 2. **Never deep-traverse, clone, or serialize the payload.** Only
14
+ * `Object.keys` (shallow) and own-key reads. See `LegacyProjectionSchema`
15
+ * for why `raw` must stay an alias.
16
+ *
17
+ * Import rules: no filesystem, network, subprocess, or OTel SDK.
18
+ */
19
+ import type { LegacyProjection, Outcome, WorkflowIds } from './envelope.js';
20
+ /** The store these payloads are written to. */
21
+ export declare const LEGACY_TELEMETRY_SOURCE_STORE = ".swarm/telemetry.jsonl";
22
+ /** Marker recorded in `legacy.unknown` when the payload is not an object. */
23
+ export declare const NON_OBJECT_PAYLOAD_MARKER = "<non-object-payload>";
24
+ /**
25
+ * Marker recorded when the payload resisted shallow introspection — a Proxy
26
+ * whose `ownKeys` trap throws, or a throwing getter. The payload is still kept
27
+ * by reference; only our description of it is degraded.
28
+ */
29
+ export declare const INTROSPECTION_FAILED_MARKER = "<payload-introspection-failed>";
30
+ /**
31
+ * The adapter rules, stated as a checkable list.
32
+ *
33
+ * These are the item-4 rules of issue #2029. They are exported as data (not
34
+ * prose in a doc file) so the static contract check and the unit tests can
35
+ * assert against the same statement of intent the implementation follows.
36
+ */
37
+ export declare const LEGACY_ADAPTER_RULES: readonly string[];
38
+ /**
39
+ * Payload keys each producer is known to emit.
40
+ *
41
+ * Derived from the emit call sites in source, NOT from captured output: a key
42
+ * whose value is `undefined` is elided by `JSON.stringify`, so a captured line
43
+ * under-reports the producer's key set. Listing those keys here is precisely
44
+ * what makes `legacy.unknown` non-vacuous — `delegation_end` naming `model`,
45
+ * `gate` and `retry_index` lets the adapter say "the producer did not know
46
+ * these" instead of silently omitting them.
47
+ */
48
+ export declare const KNOWN_TELEMETRY_KEYS: Readonly<Record<string, readonly string[]>>;
49
+ export declare function adaptLegacyTelemetryPayload(kind: string, data: unknown, knownKeys: readonly string[]): LegacyProjection;
50
+ /**
51
+ * Extract recognized correlation IDs from a legacy payload.
52
+ *
53
+ * Shallow, never throws, and NEVER synthesizes: an ID that is absent, empty, or
54
+ * of the wrong type stays `undefined`. Only the mappings a producer actually
55
+ * populates are implemented — `sessionId` to `hostSessionId`, `taskId`, `phase`
56
+ * to `phaseId`, `laneId`, `batchId`. The remaining eight recognized IDs have no
57
+ * current producer, and inventing an extraction for them would manufacture
58
+ * exactly the joins issue #2029 item 2 forbids.
59
+ */
60
+ export declare function extractWorkflowIds(data: unknown): WorkflowIds;
61
+ /**
62
+ * Extract terminal disposition from a legacy payload.
63
+ *
64
+ * Shallow and never throws. `durationMs` is deliberately never populated: no
65
+ * current producer reports a duration, and deriving one would fabricate a
66
+ * measurement.
67
+ */
68
+ export declare function extractOutcome(data: unknown): Outcome;
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Observation construction and the legacy line projection (issue #2029).
3
+ *
4
+ * Import rules: no filesystem, network, subprocess, or OTel SDK. Everything here
5
+ * is synchronous and allocation-only.
6
+ *
7
+ * ## Module state
8
+ *
9
+ * Four process-scoped variables. None is session-keyed, so AGENTS.md invariant 8
10
+ * (session-scoped state must be keyed by `sessionID` and bounded) does not
11
+ * apply: `_provenance` and `_lineage` are single small frozen-by-convention
12
+ * records describing THIS process, `_sampleRate` is a scalar, and
13
+ * `_writerSequence` is a scalar counter. None of them grows with session count,
14
+ * so there is nothing to evict. All four have a reset seam
15
+ * ({@link resetObservabilityForTesting}) so no test's assertions become order
16
+ * dependent.
17
+ */
18
+ import { type ObservabilityEvent, type Provenance } from './envelope.js';
19
+ /** Violation code stamped on a fallback event. */
20
+ export declare const OBSERVATION_BUILD_FAILED = "observation_build_failed";
21
+ /** Input to {@link initObservability}. */
22
+ export interface InitObservabilityInput {
23
+ /** Absolute project root. Pseudonymized, never stored. */
24
+ directory?: string;
25
+ /** Cohort label (e.g. a swarm id). Pseudonymized together with `directory`. */
26
+ cohortLabel?: string;
27
+ /** Worktree identity. Pseudonymized, never stored. */
28
+ worktreeId?: string;
29
+ provenance?: Provenance;
30
+ sampleRate?: number;
31
+ }
32
+ /**
33
+ * Install process provenance and lineage.
34
+ *
35
+ * Performs ZERO I/O — it hashes strings the caller already holds. That matters
36
+ * for AGENTS.md invariant 1: this runs on the plugin init path, and init-path
37
+ * work must be fast, bounded and side-effect-minimal.
38
+ *
39
+ * Lineage refs are computed ONCE here and reused by every subsequent
40
+ * observation. Computing them per-emit would put three SHA-256 digests on the
41
+ * hot path for values that never change within a process.
42
+ *
43
+ * NEVER THROWS. The whole body is guarded: a failure here must not prevent the
44
+ * plugin manifest from being returned (invariant 1, fail-open).
45
+ */
46
+ export declare function initObservability(input: InitObservabilityInput): void;
47
+ /**
48
+ * Build the canonical observation for one emit.
49
+ *
50
+ * **NEVER THROWS.** Not on a circular object, not on a function / `Symbol` /
51
+ * `BigInt` payload, not on an unknown kind, not on `null` or `undefined` data.
52
+ * It never calls `JSON.stringify`, never clones, and never deep-traverses
53
+ * `data` — `src/telemetry.test.ts:137-162` emits exactly those payloads and
54
+ * asserts `emit()` does not throw, and `src/telemetry.ts:266` documents the
55
+ * never-throw guarantee. If anything inside fails, a minimal valid event is
56
+ * returned carrying `relationshipViolations: ['observation_build_failed']`.
57
+ *
58
+ * An UNCATALOGUED kind is classified (`category: 'unrecognized'`), never
59
+ * dropped. Preventing new unrecognized kinds is the static contract check's job,
60
+ * not a runtime drop.
61
+ *
62
+ * `occurredAt` is set equal to `observedAt`. That is a recorded finding, not an
63
+ * invention: no current producer supplies a distinct time at which the described
64
+ * thing happened, so claiming one would fabricate precision the source does not
65
+ * have.
66
+ */
67
+ export declare function createObservation(kind: string, data: unknown): ObservabilityEvent;
68
+ /**
69
+ * Project a canonical event onto the legacy `.swarm/telemetry.jsonl` record.
70
+ *
71
+ * ## This projection is LOSSY and LEGACY-PINNED
72
+ *
73
+ * The envelope's non-legacy fields — `eventId`, `trace` (`traceId`, `spanId`,
74
+ * `parentSpanId`, `links`), `lineage`, `provenance`, `policy`, `writerSequence`,
75
+ * `schemaVersion` and `relationshipViolations` — are **currently DISCARDED**.
76
+ * Nothing in this change consumes them; their consumer lands in **#2047**. That
77
+ * is stated here rather than buried: a reader of this function must not conclude
78
+ * the written line is the canonical record. It is not.
79
+ *
80
+ * ## Byte-for-byte preservation
81
+ *
82
+ * The caller's object is spread **LAST**, exactly as the pre-change inline line
83
+ * construction in `src/telemetry.ts` `emit()` did. Three properties depend on
84
+ * that ordering, and all three are observable in
85
+ * `tests/fixtures/observability/telemetry-lines-golden.json` (issue #2029 item
86
+ * 5 arm (a) — "preserve the existing output"):
87
+ *
88
+ * 1. **Caller key order** is preserved after `timestamp` and `event`.
89
+ * 2. **Caller key collisions win on value.** `conflict-resolution.ts:55-66`
90
+ * supplies its own `timestamp` and `type`; the caller's `timestamp` value
91
+ * overwrites the envelope's while the key keeps position 1.
92
+ * 3. **`undefined`-key elision** is unchanged, because the caller's own object
93
+ * is spread rather than a reconstruction of it.
94
+ *
95
+ * `timestamp` is taken from `event.observedAt` — a real, load-bearing data
96
+ * dependency on the canonical event, which is why this composition is not the
97
+ * identity function on the emit path.
98
+ *
99
+ * A non-object, `null`, or array payload yields just `{ timestamp, event }`,
100
+ * matching what `JSON.stringify({ timestamp, event, ...data })` produces for
101
+ * those inputs.
102
+ */
103
+ export declare function toLegacyTelemetryLine(event: ObservabilityEvent): Record<string, unknown>;
104
+ /**
105
+ * Reset all four module-scoped variables.
106
+ *
107
+ * Called alongside `resetTelemetryForTesting` so no test's assertions become
108
+ * dependent on how many events an earlier test emitted.
109
+ *
110
+ * @internal - For testing only
111
+ */
112
+ export declare function resetObservabilityForTesting(): void;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * External-convention attribute mappings (issue #2029 item 6).
3
+ *
4
+ * These are INERT LOOKUP TABLES. There is no OpenTelemetry SDK dependency, no
5
+ * exporter, and no runtime consumer in this change: the tables are consumed as
6
+ * data by the static contract check (which asserts every catalog entry records a
7
+ * mapping decision) and by `docs/observability-event-contract.md`. The runtime
8
+ * consumer lands in #2049.
9
+ *
10
+ * Import rules: no filesystem, network, subprocess, or OTel SDK.
11
+ */
12
+ import type { OtelMappingKind } from './catalog.js';
13
+ /**
14
+ * OpenTelemetry semantic-convention generation these GenAI attribute names track.
15
+ *
16
+ * Pinned SEPARATELY from `OBSERVABILITY_SCHEMA_VERSION`. That separation is the
17
+ * whole point of issue #2029 item 6: the GenAI conventions are an external,
18
+ * still-unstable vocabulary. If they were versioned together, an upstream rename
19
+ * would force a bump of the internal envelope version and every consumer would
20
+ * be told the domain shape changed when nothing about the domain changed.
21
+ * External convention churn must never change internal domain state.
22
+ */
23
+ export declare const OTEL_GENAI_MAPPING_VERSION = "1.29.0";
24
+ /**
25
+ * OpenInference specification generation these attribute names track. Pinned
26
+ * separately from both {@link OTEL_GENAI_MAPPING_VERSION} and
27
+ * `OBSERVABILITY_SCHEMA_VERSION`, for the same reason.
28
+ */
29
+ export declare const OPENINFERENCE_MAPPING_VERSION = "0.1.14";
30
+ /**
31
+ * Internal envelope path to OTel GenAI attribute name.
32
+ *
33
+ * Keys are dotted paths into `ObservabilityEvent`. `legacy.raw.*` paths reach
34
+ * into the aliased producer payload, which is where token counts and the
35
+ * resolved model live today.
36
+ */
37
+ export declare const OTEL_GENAI_ATTRIBUTES: Readonly<Record<string, string>>;
38
+ /** Internal envelope path to OpenInference attribute name. */
39
+ export declare const OPENINFERENCE_ATTRIBUTES: Readonly<Record<string, string>>;
40
+ /**
41
+ * Resolve the attribute table a catalog entry projects onto.
42
+ *
43
+ * `'none'` is a real, recorded decision — "this kind has no external
44
+ * equivalent" — not a missing value. It returns an empty table, never
45
+ * `undefined`, so a consumer never has to distinguish "no mapping" from "not
46
+ * asked".
47
+ */
48
+ export declare function mappingForEntry(mapping: OtelMappingKind): Readonly<Record<string, string>>;
@@ -0,0 +1,44 @@
1
+ import type { ObservabilityEvent } from './envelope.js';
2
+ /** Verdict from {@link validateEventRelationships}. */
3
+ export type RelationshipValidationResult = {
4
+ ok: true;
5
+ } | {
6
+ ok: false;
7
+ violations: string[];
8
+ };
9
+ /**
10
+ * Stable violation codes.
11
+ *
12
+ * Codes are machine-readable and must not be renamed once emitted — they are the
13
+ * join key a downstream consumer will group on. The suffix after `:` is the
14
+ * offending identifier or link index.
15
+ */
16
+ export declare const RELATIONSHIP_VIOLATION_CODES: Readonly<{
17
+ unknownKind: "unknown_kind";
18
+ requiredWorkflowIdMissing: "required_workflow_id_missing";
19
+ forbiddenWorkflowIdPresent: "forbidden_workflow_id_present";
20
+ parentSpanNotAllowed: "parent_span_not_allowed";
21
+ parentSpanMissing: "parent_span_missing";
22
+ malformedParentSpanId: "malformed_parent_span_id";
23
+ linksNotAllowed: "links_not_allowed";
24
+ malformedLinkTraceId: "malformed_link_trace_id";
25
+ malformedLinkSpanId: "malformed_link_span_id";
26
+ validationFailed: "relationship_validation_failed";
27
+ }>;
28
+ /**
29
+ * Validate an event against its catalogued relationship rules.
30
+ *
31
+ * Checks, in order:
32
+ * (a) the kind is catalogued;
33
+ * (b) every `requiredWorkflowIds` entry is present;
34
+ * (c) no `forbiddenWorkflowIds` entry is present (a present one means an ID
35
+ * was manufactured upstream — issue #2029 item 2);
36
+ * (d) a parent span is absent when the kind does not take one;
37
+ * (e) a parent span is present when the kind requires one;
38
+ * (f) a present parent span is a well-formed W3C span id;
39
+ * (g) links are absent when the kind does not allow them;
40
+ * (h) every link carries a well-formed trace/span id pair.
41
+ *
42
+ * @returns `{ ok: true }` or `{ ok: false, violations }`. Never throws.
43
+ */
44
+ export declare function validateEventRelationships(event: ObservabilityEvent): RelationshipValidationResult;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Sampling and metric-cardinality policy (issue #2029).
3
+ *
4
+ * Import rules: no filesystem, network, subprocess, or OTel SDK.
5
+ */
6
+ /**
7
+ * Default sample rate.
8
+ *
9
+ * `1` = sample everything. This PR introduces no dropping: a drop that no
10
+ * consumer can see is exactly the silent data loss the issue names, and the
11
+ * consumer that would make a drop observable lands in #2047.
12
+ */
13
+ export declare const DEFAULT_SAMPLE_RATE = 1;
14
+ /**
15
+ * Decide whether a trace is sampled.
16
+ *
17
+ * **Deterministic by construction.** The decision is a pure function of the
18
+ * trace id and the rate: the LAST 8 hex characters of the trace id are read as
19
+ * an unsigned integer and compared against `rate * 0xffffffff`. The same trace
20
+ * therefore samples identically in every process, on every host, and across
21
+ * restarts — no shared state, no RNG, no coordination. That is what makes a
22
+ * sampled distributed trace complete rather than a set of disconnected
23
+ * fragments, and it is why the decision is not `Math.random()`.
24
+ *
25
+ * **CAVEAT — that property is currently VACUOUS in this system, and saying so
26
+ * is the point.** `createObservation` mints a FRESH `traceId` per event, with
27
+ * `links: []` and no `parentSpanId`, so every event today is its own
28
+ * single-span trace. At a rate below 1 the decision is therefore independent
29
+ * PER EVENT, not per logical trace: a "trace" never spans two events, so
30
+ * nothing can be kept whole or torn apart. Trace continuation — propagating one
31
+ * trace id across the events that belong together — is #2047's work, and only
32
+ * then does trace-coherent sampling become a real property rather than a
33
+ * latent one. Nothing is dropped today regardless: {@link DEFAULT_SAMPLE_RATE}
34
+ * is `1`.
35
+ *
36
+ * **Fail-open.** Every unusable input returns `true`. Dropping an event because
37
+ * an id was malformed would lose data silently, which is the failure mode this
38
+ * contract exists to prevent. A non-finite rate also fails open, for the same
39
+ * reason: `NaN` would otherwise fall through both bounds and make every
40
+ * comparison `false`, silently dropping everything.
41
+ *
42
+ * @param traceId - 32 lowercase hex characters.
43
+ * @param rate - `>= 1` samples everything, `<= 0` samples nothing.
44
+ */
45
+ export declare function shouldSample(traceId: string, rate: number): boolean;
46
+ /**
47
+ * The only labels permitted on a metric.
48
+ *
49
+ * Every member is bounded: a small, enumerable set of values that cannot grow
50
+ * with traffic. The rule this enforces, from issue #2029: **IDs, paths, users,
51
+ * tasks, and repositories belong in traces and logs, not metric labels.** A
52
+ * single unbounded label multiplies the time-series count by its cardinality and
53
+ * takes a metrics backend down; the trace already carries that detail, keyed to
54
+ * the same event.
55
+ */
56
+ export declare const METRIC_LABEL_ALLOWLIST: readonly string[];
57
+ /** Verdict from {@link assertBoundedCardinality}. */
58
+ export type CardinalityResult = {
59
+ ok: true;
60
+ } | {
61
+ ok: false;
62
+ violations: string[];
63
+ };
64
+ /** Stable violation codes, suffixed with the offending label. */
65
+ export declare const CARDINALITY_VIOLATION_CODES: Readonly<{
66
+ notAllowlisted: "label_not_allowlisted";
67
+ highCardinalityShape: "high_cardinality_label_shape";
68
+ }>;
69
+ /**
70
+ * Check a metric's label set against the allowlist and against high-cardinality
71
+ * shapes.
72
+ *
73
+ * RETURNS a verdict; never throws. A label can produce both codes — the shape
74
+ * check runs independently of the allowlist check so that a newly-allowlisted
75
+ * label with an unbounded shape is still reported.
76
+ */
77
+ export declare function assertBoundedCardinality(labels: readonly string[]): CardinalityResult;
@@ -95,10 +95,10 @@ export declare const PhaseArchitectureSummarySchema: z.ZodObject<{
95
95
  export type PhaseArchitectureSummary = z.infer<typeof PhaseArchitectureSummarySchema>;
96
96
  export declare const SupervisorFindingSchema: z.ZodObject<{
97
97
  severity: z.ZodEnum<{
98
+ critical: "critical";
98
99
  low: "low";
99
100
  medium: "medium";
100
101
  high: "high";
101
- critical: "critical";
102
102
  }>;
103
103
  category: z.ZodString;
104
104
  agents: z.ZodDefault<z.ZodArray<z.ZodString>>;
@@ -135,10 +135,10 @@ export declare const ArchitectureSupervisorReportSchema: z.ZodObject<{
135
135
  }>;
136
136
  findings: z.ZodDefault<z.ZodArray<z.ZodObject<{
137
137
  severity: z.ZodEnum<{
138
+ critical: "critical";
138
139
  low: "low";
139
140
  medium: "medium";
140
141
  high: "high";
141
- critical: "critical";
142
142
  }>;
143
143
  category: z.ZodString;
144
144
  agents: z.ZodDefault<z.ZodArray<z.ZodString>>;
@@ -1,3 +1,4 @@
1
+ import { createObservation, toLegacyTelemetryLine } from './observability/index.js';
1
2
  import type { DelegationCostFields } from './services/cost-accounting.js';
2
3
  export type TelemetryEvent = 'session_started' | 'session_ended' | 'agent_activated' | 'delegation_begin' | 'delegation_end' | 'task_state_changed' | 'gate_passed' | 'gate_failed' | 'gate_parse_error' | 'reviewer_gate_decision' | 'phase_changed' | 'budget_updated' | 'model_fallback' | 'hard_limit_hit' | 'revision_limit_hit' | 'loop_detected' | 'no_op_strong_warning' | 'scope_violation' | 'qa_skip_violation' | 'heartbeat' | 'turbo_mode_changed' | 'auto_oversight_escalation' | 'environment_detected' | 'evidence_lock_acquired' | 'evidence_lock_contended' | 'evidence_lock_stale_recovered' | 'plan_ledger_cas_retry' | 'plan_md_write_failed' | 'snapshot_failed' | 'gate_denial_loop'
3
4
  /**
@@ -25,7 +26,7 @@ export type TelemetryEvent = 'session_started' | 'session_ended' | 'agent_activa
25
26
  * `prm_hard_stop` TRIGGER emitted by `src/prm/escalation.ts`. A trigger with
26
27
  * no matching delivery means the containment never reached the agent.
27
28
  */
28
- | 'prm_hard_stop_delivered';
29
+ | 'prm_hard_stop_delivered' | 'agent_conflict_detected';
29
30
  /** Stable classification for how a reviewer-gate decision was established. */
30
31
  export type ReviewerGateEvidenceKind = 'genuine' | 'fallback' | 'data_quality' | 'block';
31
32
  /**
@@ -157,4 +158,6 @@ export declare const _internals: {
157
158
  emit: typeof emit;
158
159
  rotateTelemetryIfNeeded: typeof rotateTelemetryIfNeeded;
159
160
  heartbeatListenerCount: () => number;
161
+ createObservation: typeof createObservation;
162
+ toLegacyTelemetryLine: typeof toLegacyTelemetryLine;
160
163
  };
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Shared primitives for hashing tool-call arguments (issue #2060 follow-ups
3
+ * F-009 / F-010).
4
+ *
5
+ * # Why this exists
6
+ *
7
+ * Two subsystems hash tool arguments to detect "the agent is repeating
8
+ * itself": `hashArgsForSpiral` in `hooks/adversarial-detector.ts` (spiral
9
+ * advisory) and `hashArgs` in `hooks/guardrails/file-authority.ts` (which
10
+ * feeds the consecutive-repetition circuit breaker in
11
+ * `hooks/guardrails/tool-before.ts` — a path that THROWS, not warns).
12
+ *
13
+ * Both need the same two primitives. They live here rather than as two inline
14
+ * copies for the same reason `src/utils/stable-stringify.ts` was extracted: a
15
+ * bug class that exists at two call sites must be fixed once, in one place,
16
+ * and tested once. Keep them here; do not re-inline a copy at a call site.
17
+ *
18
+ * The two call sites differ only in the OUTPUT SHAPE they need — the detector
19
+ * wants a compact base-36 string, file-authority wants a `number` — so
20
+ * `boundedBunHash` returns `bigint` (matching `bunHash`) and each caller
21
+ * formats it.
22
+ */
23
+ /**
24
+ * Cap on the amount of input fed into `bunHash`.
25
+ *
26
+ * Both call sites run synchronously on a per-tool-call hot path, and payloads
27
+ * (patch bodies, file writes) can reach ~1 MB. Node's `bunHash` fallback
28
+ * (djb2, see `bun-compat.ts`) is genuinely reachable in production — the
29
+ * OpenCode plugin host and the Desktop sidecar can run under Node — and is
30
+ * O(n): ~141 ms for a 2 MB input versus ~5 ms for 64 KB. The threat model is
31
+ * "same tool, same args, repeatedly", not adversarial collision resistance,
32
+ * so bounding the hashed input keeps worst-case per-call cost flat.
33
+ *
34
+ * NOTE ON UNITS: this bounds `String.prototype.length`, i.e. UTF-16 code
35
+ * units, not encoded bytes. It is a cost bound, not a byte-exact limit; a
36
+ * non-BMP-heavy string can encode to more bytes than the name suggests. That
37
+ * is fine — the point is a fixed ceiling, and the ceiling is fixed.
38
+ */
39
+ export declare const HASH_INPUT_CAP_BYTES: number;
40
+ /**
41
+ * Reduces an arbitrarily long string to a bounded, length-prefixed
42
+ * head-and-tail sample suitable as hash input.
43
+ *
44
+ * ## Why not a bare prefix
45
+ *
46
+ * The obvious bound — `input.slice(0, CAP)` — makes every pair of inputs that
47
+ * share a `CAP`-length prefix hash identically. For the circuit-breaker call
48
+ * site that is a false-positive engine: ten consecutive large writes that
49
+ * share a boilerplate header but differ in their (appended) bodies look like
50
+ * ten identical calls and the breaker throws. Sampling the head AND the tail
51
+ * removes the whole append-collision class, and sampling the head removes the
52
+ * prepend-collision class, at exactly the same bounded cost.
53
+ *
54
+ * ## Why the length prefix is load-bearing
55
+ *
56
+ * It is not decoration. Without it the two branches below can collide across
57
+ * the cap boundary: an input of exactly `CAP` characters passes through
58
+ * untransformed, while a longer input produces a `CAP`-character head+tail
59
+ * concatenation — two different inputs, one identical hash input. Prefixing
60
+ * the true length separates the classes. It also makes the under-cap branch
61
+ * injective, so this transform adds ZERO collisions for inputs at or below
62
+ * the cap.
63
+ *
64
+ * ## Residual, accepted lossiness
65
+ *
66
+ * Two inputs longer than the cap collide only if they have the SAME total
67
+ * length AND the same first `CAP/2` characters AND the same last `CAP/2`
68
+ * characters, differing only in the discarded middle. That is unavoidable for
69
+ * any fixed-cost sampler and is the deliberate trade documented on
70
+ * `HASH_INPUT_CAP_BYTES`.
71
+ *
72
+ * Cannot throw: `String.prototype.slice` and template interpolation of a
73
+ * number are total.
74
+ */
75
+ export declare function sampleForHash(input: string): string;
76
+ /**
77
+ * `bunHash` over a bounded head+tail sample of `input`.
78
+ *
79
+ * Returns `bigint` (the `bunHash` shape) so each caller can format it for its
80
+ * own storage: the detector uses `.toString(36)`, file-authority uses
81
+ * `Number(...)`.
82
+ *
83
+ * Cannot throw for a string input: `sampleForHash` is total and `bunHash`
84
+ * on a string is `TextEncoder.encode` plus BigInt arithmetic (or `Bun.hash`)
85
+ * — no throwing path. Callers rely on this: both use it inside a `catch`
86
+ * block, where a second throw would escape into a hook.
87
+ */
88
+ export declare function boundedBunHash(input: string): bigint;
89
+ /**
90
+ * Shallow, non-recursive structural summary of a value's own enumerable keys,
91
+ * used only as a fallback discriminator when `stableCanonicalStringify`
92
+ * throws (issue #2060 follow-up F-009).
93
+ *
94
+ * Before this existed, both call sites answered an unstringifiable argument
95
+ * with a CONSTANT ('h:fallback' / `0`). That made every distinct-but-
96
+ * unserializable argument collide, so N consecutive calls with genuinely
97
+ * different arguments looked identical and fired a false positive — a spiral
98
+ * advisory in one case, a thrown circuit breaker in the other.
99
+ *
100
+ * Deliberately shallow (no recursion into nested values) so it cannot itself
101
+ * throw on the very inputs that broke `stableCanonicalStringify`: recursing
102
+ * into a cyclic reference would revisit the cycle, and BigInt values are
103
+ * handled fine by `typeof` / `String()` (unlike `JSON.stringify`, which
104
+ * throws on them). `Object.keys` and `typeof` cannot throw for ordinary
105
+ * objects; the outer try/catch guards only against exotic Proxy traps, and
106
+ * even then identical arguments still collide, so true-positive repetition
107
+ * detection is preserved.
108
+ *
109
+ * This is a DISCRIMINATOR, not an identity function. It is only ever used to
110
+ * break up false collisions on a detection path — never as a fail-closed
111
+ * identity (see the caller guidance on `stableCanonicalStringify`).
112
+ */
113
+ export declare function coarseObjectDiscriminator(args: unknown): string;