@typedstandards/civic-typed-harness 0.5.0 → 0.7.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/CHANGELOG.md CHANGED
@@ -3,6 +3,136 @@
3
3
  Factual record of what changed per published version. Section references are
4
4
  to the Typed Standards specification unless noted otherwise.
5
5
 
6
+ ## 0.7.0 — 2026-10-15
7
+
8
+ - **The seam inside the harness** ([civic-ai-tools#244](https://github.com/npstorey/civic-ai-tools/issues/244) P2).
9
+ `src/capture/provenance.ts` and `src/capture/data-sources.ts` import
10
+ nothing from `src/format/`, type-only imports included. The capture
11
+ builders take the vocabulary, the sixteen `civic:` term names, the source
12
+ registry, the fallback and skill source ids and the dataset-keyed fact as
13
+ required parameters.
14
+ - **What moved.** `buildProvenanceGraph`, `ProvenanceConfig` and
15
+ `CIVICAITOOLS_PROVENANCE_CONFIG` moved to `src/civic/provenance.ts`;
16
+ `buildDataSources`, `resolveToolSource` and `DataSourceOptions` moved to
17
+ `src/civic/data-sources.ts`. These modules apply the civic defaults
18
+ (`CIVIC_VOCABULARY`, `CIVIC_SOURCE_REGISTRY`, `FALLBACK_SOURCE_ID`,
19
+ `civicToolSourceResolver`, `isDatasetKeyedSource`) and call the capture
20
+ builders. The compile-only pin `data-sources.assert.ts` moved with
21
+ `buildDataSources`, byte-unchanged.
22
+ - **The API is unchanged.** The package entry exports the same names with
23
+ the same signatures, and the capture builders are not exported. The golden
24
+ suites pass with no fixture edited.
25
+ - `src/purity.test.ts` gains the rule: a capture module imports no module from
26
+ outside `src/capture/`. A package import, such as
27
+ `@typedstandards/produce-core`, is allowed.
28
+
29
+ The record fields of [ADR-0033](../../docs/adr/0033-composed-harness-record-fields.md)
30
+ (sprint R2, H2). Signed bytes move only where one of these inputs is passed,
31
+ where a call's source is not in the registry, and in a datHere environment
32
+ block that no longer states two placeholders.
33
+
34
+ - **Every call is attributed to its own source** (ADR-0033 §A). A call tagged
35
+ with a source the registry lacks was attributed to Socrata's agent — in a
36
+ run where Socrata made no call, an agent the graph did not contain — and is
37
+ now attributed to its own source's agent. An untagged span resolves by its
38
+ tool name, as `dataSources` already did (`ProvenanceConfig.resolver`, new,
39
+ default `civicToolSourceResolver`). A span neither identifies is attributed
40
+ to the new `UNKNOWN_SOURCE_ID` (`'unknown'`), the value the reference app
41
+ writes for a tool it cannot resolve; `resolveToolSource` and
42
+ `buildDataSources` default to it too, so an unknown call mints no
43
+ `dataSources` entry. `FALLBACK_SOURCE_ID` (`'socrata'`) stays exported and is
44
+ no longer a default for attribution. The capture builder's
45
+ `ProvenanceBuildConfig` gains a required `resolver`.
46
+ - **Each call states its upstream name, alias and server** (§B): the span
47
+ attributes `tool.upstream_name`, `mcp.alias` and `mcp.server.id` become
48
+ three new terms, `civic:upstreamToolName`, `civic:sourceAlias` and
49
+ `civic:serverId`, appended to the tool-call activity when present.
50
+ `ProvenanceTerms` gains the three keys.
51
+ - **The environment block states `temperature` and `toolDefinitions` only
52
+ when passed** (§C; [civic-ai-tools#240](https://github.com/npstorey/civic-ai-tools/issues/240)):
53
+ `buildDatHereEnvironment` takes an optional fourth argument and
54
+ `DatHerePolicyInput` two optional fields. Both keep their 0.6.0 key
55
+ positions, so passing the recorded `0` and `[]` reproduces an earlier
56
+ package byte for byte. Specification §8.7.1 requirement 3 is amended in
57
+ v0.1.11 to match.
58
+ - **The run-time servers win** (§D; civic-ai-tools-website#495):
59
+ `DatHerePolicyInput.runMcpServers` is preferred over `mcpServers` and
60
+ `skillMcpServerUrl`; `withRunServerAddresses(registry, servers)` (new) gives
61
+ the graph's source agents the run's addresses.
62
+ - **A new `org.civicaitools.composition` block** (§E;
63
+ civic-ai-tools-website#475): `assertedDate`, `sourcesOffered` and
64
+ `components`, under every content profile, emitted only when one is
65
+ passed. `COMPOSITION_EXTENSION_KEY`, `SOURCE_OFFERED_REASONS`,
66
+ `GUIDANCE_ORIGINS`, `buildCompositionExtension` and `RecordFieldError`
67
+ (new). A value outside the record's form is refused, never dropped.
68
+ - **`extractRunAttributes(trace)`** (new, capture): reads `analysis.asserted_date`,
69
+ `analysis.mcp_servers`, `analysis.tool_definitions`,
70
+ `analysis.sources_offered` and `analysis.components` from the trace's root
71
+ span; a present attribute that does not parse throws `RunAttributeError`.
72
+ - **`CIVICAITOOLS_TRACE_CONFIG` moved to `src/civic/trace.ts`**
73
+ ([civic-ai-tools#246](https://github.com/npstorey/civic-ai-tools/issues/246)),
74
+ re-exported under the same name. `src/purity.test.ts` gains the rule: no
75
+ capture module exports a value binding.
76
+ - **The goldens.** No frozen fixture is edited, and `src/frozen-fixtures.test.ts`
77
+ (new) pins the sha256 of `website-golden.json` and `reference-golden.json`.
78
+ The website golden's derived graph moves in one field (tool-4's
79
+ `prov:wasAssociatedWith`), reached by a second named lift with its count
80
+ asserted; `span-carrying-golden.json`'s two recorded hashes are regenerated
81
+ with a dated note. `reference-golden.json`'s eight cases reproduce byte for
82
+ byte given their recorded environment values, and a new-default leg pins
83
+ the bytes without them.
84
+ - `capture/data-sources.test.ts`'s "Unknown tool with no trace span defaults
85
+ to socrata (pre-M9.1 backward compat)" is rewritten: the tool is `unknown`.
86
+
87
+ The dependency floor (sprint R2, G4).
88
+
89
+ - **Dependency floor raised:** `@typedstandards/produce-core` `^0.7.0` →
90
+ `^0.8.0`, and the `@typedstandards/verify-core` devDependency `^0.12.0` →
91
+ `^0.13.0`, the version produce-core 0.8.0 resolves. Under 0.x semver
92
+ `^0.7.0` excludes 0.8.0, so a consumer that pins produce-core `^0.8.0`
93
+ would install a second copy of each core beside this package's.
94
+ - **What the two releases bring.** produce-core 0.8.0 emits four more
95
+ attestation sub-types and adds `EnvelopeInput.vcsRef` and raw-bytes/v1 over
96
+ a BlobRef; an input that supplies none of them builds the same bytes.
97
+ verify-core 0.13.0 adds the `superseded` lifecycle status, the key binding
98
+ in `verifyLifecycleChain` and `checkAttestationNode`. The harness reads none
99
+ of these.
100
+ - The lockfile resolves produce-core 0.8.0 and verify-core 0.13.0, one copy
101
+ each, and no other entry moves. The golden byte-compat suite passes
102
+ unchanged.
103
+ - A consumer that pins produce-core 0.7.x no longer shares its copy with this
104
+ package.
105
+ - **README:** the runtime-dependency line names `^0.8.0`.
106
+
107
+ ## 0.6.0 — 2026-09-26
108
+
109
+ The list is derived from
110
+ `git log 65e8a1d..<this release> -- packages/civic-typed-harness/` — `65e8a1d`
111
+ is the 0.5.0 release's last commit, its dating fix. No harness source changes
112
+ in that range.
113
+
114
+ - **Dependency floor raised:** `@typedstandards/produce-core` `^0.3.0 ||
115
+ ^0.4.0` → `^0.7.0`, and the `@typedstandards/verify-core` devDependency
116
+ `^0.9.0` → `^0.12.0`, the version produce-core 0.7.0 resolves. The two move
117
+ together so the install holds one copy of verify-core: with only the
118
+ devDependency moved, produce-core 0.3.0 kept verify-core 0.9.0 and the
119
+ harness tests imported a second copy. The raise went to `^0.6.0` /
120
+ `^0.11.0` first ([civic-ai-tools#232](https://github.com/npstorey/civic-ai-tools/issues/232))
121
+ and was not published at that range.
122
+ - **What the two releases bring.** verify-core 0.11.0 is the first release
123
+ that exports `KNOWN_TYPE_URIS`, which the hub's `check:verifier-subtype-set`
124
+ compares with the specification's §8.12.1 table. verify-core 0.12.0 (the
125
+ certificate-chain split, typedstandards#100) adds a member to
126
+ `ChainFailReason` and to `Rfc3161FailReason`; the harness reads neither.
127
+ produce-core 0.7.0 changes none of its own source.
128
+ - The lockfile resolves produce-core 0.7.0 and verify-core 0.12.0, one copy
129
+ each, and no other entry moves. The golden byte-compat suite passes
130
+ unchanged.
131
+ - A consumer that pins produce-core 0.3.x through 0.6.x no longer shares its
132
+ copy with this package: under 0.x semver `^0.7.0` excludes them.
133
+ - **README:** the runtime-dependency line named produce-core `^0.2.0`, a range
134
+ this package has not declared since 0.3.0. It names `^0.7.0`.
135
+
6
136
  ## 0.5.0 — 2026-09-18
7
137
 
8
138
  The list is derived from
package/README.md CHANGED
@@ -9,10 +9,11 @@ The civic **domain harness** for [Typed Standards](../../docs/architecture/typed
9
9
  | Module group | Modules | What it is |
10
10
  |---|---|---|
11
11
  | **`src/format/`** — the civic format-extension | `vocabulary.ts`, `sources.ts`, `dathere.ts`, `profiles.ts` | What extends the *standard*: the `civic:` JSON-LD namespace and `urn:civic-record:` id scheme (both in two eras — new emissions mint the settlement-era terms, the prior-era terms stay exported and reproducible because they are frozen inside already-signed records; spec Appendix J), the civic source registry, the datHere envelope-policy derivations (producerProfile, canonicalization rule, summary emission, environment extension), and the captureMethod vocabulary surface. |
12
- | **`src/capture/`** — the capture machinery | `trace.ts`, `skill-metadata.ts`, `data-sources.ts`, `provenance.ts` | What *produces* under it: the OTel `TraceBuilder`, skill-metadata extraction, data-source population, and the provenance **builder** — which imports its vocabulary from the format group and walks the trace. |
12
+ | **`src/capture/`** — the capture machinery | `trace.ts`, `skill-metadata.ts`, `run-attributes.ts`, `data-sources.ts`, `provenance.ts` | What *produces* under it: the OTel `TraceBuilder`, skill-metadata extraction, run-attribute extraction (`extractRunAttributes`), data-source population, and the provenance **builder**, which walks the trace. Capture imports no module from outside `src/capture/`; its one import beyond it is the `@typedstandards/produce-core` package. The vocabulary, the term names, the source registry and their defaults arrive as parameters, and capture defines no deployment value (it exports no `const`). |
13
+ | **`src/civic/`** — the civic defaults over capture | `data-sources.ts`, `provenance.ts`, `run-servers.ts`, `trace.ts` | The package's `buildDataSources`, `resolveToolSource` and `buildProvenanceGraph`, with `DataSourceOptions`, `ProvenanceConfig` and `CIVICAITOOLS_PROVENANCE_CONFIG`: they pass the format group's vocabulary, terms and registry to the capture builders and apply the civic defaults. Also `withRunServerAddresses` and the reference deployment's trace identity, `CIVICAITOOLS_TRACE_CONFIG`. |
13
14
  | **`src/rubric/`** — the adversarial rubric | `adversarial-eval-core.ts` | The civic six-criterion evaluation rubric (pure core: rubric text, pinned version hash, prompt builder, response parser). The model runner and result emission stay implementation-side. |
14
15
 
15
- **The internal boundary is load-bearing:** no capture module defines vocabulary, and no format-extension module walks a trace — this sentence reserves a future split of the format-extension group into its own package (a civic extension *of Typed Standards*, distinct from the civic *harness* proper) as a mechanical move rather than a re-partition. The boundary is enforced by `src/purity.test.ts`.
16
+ **The internal boundary is load-bearing:** no capture module defines vocabulary or imports a module from outside `src/capture/` (a package import, `@typedstandards/produce-core`, is allowed), and no format-extension module walks a trace — this sentence reserves a future split of the format-extension group into its own package (a civic extension *of Typed Standards*, distinct from the civic *harness* proper) as a mechanical move rather than a re-partition. The boundary is enforced by `src/purity.test.ts`.
16
17
 
17
18
  ## Config, not constants — and required, never defaulted
18
19
 
@@ -45,17 +46,44 @@ const graph = buildProvenanceGraph(trace, input, CIVICAITOOLS_PROVENANCE_CONFIG)
45
46
 
46
47
  Likewise: `buildDatHereEnvironment` / `deriveDatHereEnvelopeFields` take `CIVICAITOOLS_ENVIRONMENT_CONFIG`, `new TraceBuilder(...)` takes `CIVICAITOOLS_TRACE_CONFIG`, and `isDatasetKeyedSource` / `displayNameForSource` / `formatDataSourcesSummary` take `CIVIC_SOURCE_REGISTRY`. The acceptance condition is encoded in `src/required-config.assert.ts`: reintroducing any of the removed defaults fails `npm run typecheck`.
47
48
 
49
+ ## What a record states about its run (0.7.0, [ADR-0033](../../docs/adr/0033-composed-harness-record-fields.md))
50
+
51
+ **Every call is attributed to its own source.** A tool span's source is its `mcp.source` tag; an untagged span resolves by its tool name through the resolver (`ProvenanceConfig.resolver`, default `civicToolSourceResolver`); a span neither identifies is attributed to `UNKNOWN_SOURCE_ID` (`'unknown'`), never to Socrata. The same order holds in `dataSources[]`, where an unknown call mints no entry. A caller may still pass `fallbackSourceId` explicitly, for example `'socrata'` to reproduce an earlier record of an untagged span.
52
+
53
+ **Each call states both its names, its alias and its server**, when the span carries them: `tool.upstream_name`, `mcp.alias` and `mcp.server.id` become `civic:upstreamToolName`, `civic:sourceAlias` and `civic:serverId` on the tool-call activity. `civic:toolName` stays the name the model called.
54
+
55
+ **The environment block states only what is known.** `temperature` and `toolDefinitions` are emitted only when passed (`DatHerePolicyInput.temperature`, `.toolDefinitions`; spec §8.7.1 requirement 3 as amended in v0.1.11). To reproduce a package made before 0.7.0, pass the values it recorded (`temperature: 0`, `toolDefinitions: []`); its bytes come back exactly.
56
+
57
+ **Run-time facts come from the trace.** The producer writes them on the trace's root span at run time, and `extractRunAttributes(trace)` returns them under the derivation's input names:
58
+
59
+ | Root-span attribute (JSON text unless noted) | Input | Lands in |
60
+ |---|---|---|
61
+ | `analysis.asserted_date` (plain `YYYY-MM-DD`) | `assertedDate` | `extensions["org.civicaitools.composition"].assertedDate` |
62
+ | `analysis.mcp_servers` | `runMcpServers` | `org.civicaitools.environment.mcpServers` (wins over `mcpServers` and `skillMcpServerUrl`); `withRunServerAddresses(registry, servers)` gives the graph's agents the run's addresses |
63
+ | `analysis.tool_definitions` | `toolDefinitions` | `org.civicaitools.environment.toolDefinitions` |
64
+ | `analysis.sources_offered` | `sourcesOffered` | `org.civicaitools.composition.sourcesOffered` |
65
+ | `analysis.components` | `components` | `org.civicaitools.composition.components` |
66
+
67
+ ```ts
68
+ const fields = deriveDatHereEnvelopeFields(
69
+ { model, contentProfile, summary, extensions, ...extractRunAttributes(trace) },
70
+ { host },
71
+ );
72
+ ```
73
+
74
+ An absent attribute is an absent field. A present attribute that does not parse is refused (`RunAttributeError`), and a value outside the record's form — a date that is not a calendar date, a reason outside `configured` / `not-configured` / `selected` / `not-selected`, a guidance hash that is not lowercase hex SHA-256, an origin outside `endpoint` / `inline` / `fallback` — is refused by the derivation (`RecordFieldError`), never dropped. The `org.civicaitools.composition` block is emitted under every content profile, appended after the other extensions, and only when one of its fields is passed: a package built with none of these inputs has 0.6.0's extensions bytes, except that a datHere environment block no longer states the two placeholders.
75
+
48
76
  ## Purity contract (harness-grade)
49
77
 
50
78
  No I/O, no network, no environment reads, no Node built-ins, browser-safe — anywhere in the package. Clock and RNG are permitted **in capture modules only** (span timestamps and ids are what capture *is*) and are injectable for deterministic tests. Enforced mechanically twice: `eslint.config.mjs` (`no-restricted-imports` / `-globals` / `-properties` / `-syntax`) and `src/purity.test.ts` (browser-safety + determinism + module-boundary checks under `node --test`).
51
79
 
52
- Runtime dependency: **`@typedstandards/produce-core` (`^0.2.0`) only.** The verify-core primitives the harness consumes (`sha256Hex`, `isBlobRef`, the canonicalization-rule URIs, the Q32 vocabulary table, the `CaptureMethod` type) are reached through produce-core's 0.2.0 re-exports rather than imported from `@typedstandards/verify-core` directly — one declared dependency, no phantom, and still one hashing implementation across producer, harness, and verifier by construction (produce-core re-exports the same bindings verify-core defines, verified by reference identity).
80
+ Runtime dependency: **`@typedstandards/produce-core` (`^0.8.0`) only.** The verify-core primitives the harness consumes (`sha256Hex`, `isBlobRef`, the canonicalization-rule URIs, the Q32 vocabulary table, the `CaptureMethod` type) are reached through produce-core's 0.2.0 re-exports rather than imported from `@typedstandards/verify-core` directly — one declared dependency, no phantom, and still one hashing implementation across producer, harness, and verifier by construction (produce-core re-exports the same bindings verify-core defines, verified by reference identity).
53
81
 
54
82
  > **Q32 adjacency** (open-questions registry): the captureMethod vocabulary re-exported from `src/format/profiles.ts` is verify-core's hardcoded fallback table, pending versioned, content-addressed guidance bundles. Flagged, not solved, here.
55
83
 
56
84
  ## Byte-compatibility
57
85
 
58
- The port is tested for **byte parity** against golden outputs captured from the reference implementation (`src/__fixtures__/website-golden.json`): with the reference config passed explicitly, `buildProvenanceGraph` and `buildDataSources` reproduce the app's output byte-for-byte, and `RUBRIC_VERSION_SHA256` is asserted against the exact reference digest (moving the rubric changes no hashes). Both captured fixtures predate the 2026-08-19 vocabulary settlement, so their byte-parity legs run with `PRIOR_ERA_CIVIC_VOCABULARY` injected — the fixtures are never rewritten — beside a settlement-era leg asserting the same bytes with exactly the two Appendix J literals substituted. Fixture-level byte-compat against produce-core's `reference-golden.json` (`src/golden-reproduction.test.ts`) and the produce→verify composition round-trip (`src/composition-roundtrip.test.ts`) landed in S2 P2 and run with the suite.
86
+ The port is tested for **byte parity** against golden outputs captured from the reference implementation (`src/__fixtures__/website-golden.json`): with the reference config passed explicitly, `buildProvenanceGraph` and `buildDataSources` reproduce the app's output byte-for-byte, lifted over the two behaviours the harness has deliberately changed since (the #205 address fallback in 0.5.0 and the fallback attribution in 0.7.0, each a named transform of the frozen bytes with its node count asserted; both frozen fixtures' sha256 is asserted by `src/frozen-fixtures.test.ts`), and `RUBRIC_VERSION_SHA256` is asserted against the exact reference digest (moving the rubric changes no hashes). Both captured fixtures predate the 2026-08-19 vocabulary settlement, so their byte-parity legs run with `PRIOR_ERA_CIVIC_VOCABULARY` injected — the fixtures are never rewritten — beside a settlement-era leg asserting the same bytes with exactly the two Appendix J literals substituted. Fixture-level byte-compat against produce-core's `reference-golden.json` (`src/golden-reproduction.test.ts`) and the produce→verify composition round-trip (`src/composition-roundtrip.test.ts`) landed in S2 P2 and run with the suite.
59
87
 
60
88
  ## Develop
61
89
 
@@ -1,5 +1,4 @@
1
1
  import type { DataSourceEntry } from '@typedstandards/produce-core';
2
- import { type CivicSourceRegistry, type ToolSourceResolver } from '../format/sources.ts';
3
2
  export type { DataSourceEntry };
4
3
  /** The caller-supplied per-tool-call summary the population walks alongside
5
4
  * the trace. */
@@ -23,7 +22,9 @@ export interface ToolCallSummary {
23
22
  * rejection. Added 0.4.0. */
24
23
  failureKind?: string;
25
24
  }
26
- interface TraceSpan {
25
+ /** The span shape the resolver inspects. Exported for the civic layer's
26
+ * signatures; the package entry does not export it. */
27
+ export interface TraceSpan {
27
28
  name: string;
28
29
  attributes?: Array<{
29
30
  key: string;
@@ -34,18 +35,23 @@ interface TraceSpan {
34
35
  };
35
36
  }>;
36
37
  }
37
- /** Optional knobs for `resolveToolSource` / `buildDataSources`. Defaults are
38
- * the civic demo values. */
39
- export interface DataSourceOptions {
38
+ /** Every input the population reads besides the calls and the trace, each
39
+ * one explicit. */
40
+ export interface DataSourceBuildConfig {
40
41
  /** Tool-name → source-id resolver (fallback when a span carries no
41
- * `mcp.source` attribute). Default: the civic map. */
42
- resolver?: ToolSourceResolver;
43
- /** Source registry driving catalog types, endpoints, and the
44
- * dataset-keyed vs aggregate split. Default: the civic registry. */
45
- registry?: CivicSourceRegistry;
46
- /** Source id for calls neither the trace nor the resolver can identify.
47
- * Default `socrata` (pre-M9.1 packages predate source tagging). */
48
- fallbackSourceId?: string;
42
+ * `mcp.source` attribute). */
43
+ resolver: (toolName: string) => string | undefined;
44
+ /** Source id → catalog type and, for an aggregate source, the portal URL of
45
+ * its single entry. Insertion order is emission order. */
46
+ registry: Record<string, {
47
+ catalogType: string;
48
+ aggregatePortalUrl?: string;
49
+ }>;
50
+ /** Source id for calls neither the trace nor the resolver can identify. */
51
+ fallbackSourceId: string;
52
+ /** Is this source dataset-keyed (one entry per dataset)? Supplied by the
53
+ * caller, consistent with `registry`. */
54
+ isDatasetKeyed: (sourceId: string) => boolean;
49
55
  }
50
56
  /**
51
57
  * Resolve the MCP source for a tool call. Prefers the `mcp.source` attribute
@@ -58,7 +64,7 @@ export interface DataSourceOptions {
58
64
  * flow. When the counts diverge, the static resolver still identifies the
59
65
  * source.
60
66
  */
61
- export declare function resolveToolSource(toolCall: ToolCallSummary, span: TraceSpan | undefined, resolver?: ToolSourceResolver, fallbackSourceId?: string): string;
67
+ export declare function resolveToolSourceWith(toolCall: ToolCallSummary, span: TraceSpan | undefined, resolver: (toolName: string) => string | undefined, fallbackSourceId: string): string;
62
68
  /**
63
69
  * Build the per-source evidence-package `dataSources` array.
64
70
  *
@@ -81,11 +87,8 @@ export declare function resolveToolSource(toolCall: ToolCallSummary, span: Trace
81
87
  * activities and in the caller's own `queries[]` — what it is not is an
82
88
  * assertion, inside signed bytes, that a source was reached at a timestamp.
83
89
  *
84
- * @param fallbackPortal DEPRECATED, and inert since 0.3.1: an entry states
85
- * the portal the call carried, never the run's. It stays third of five
86
- * positional parameters so existing callers keep compiling, and since 0.4.0
87
- * it also accepts `undefined`, which is what a caller that has stopped
88
- * consulting it should pass. Dropping it is a breaking change and waits for
89
- * a major.
90
+ * The run's portal is not a parameter: an entry states the portal the call
91
+ * carried, never the run's (the civic layer's `buildDataSources` keeps its
92
+ * inert `fallbackPortal` positional for existing callers).
90
93
  */
91
- export declare function buildDataSources(toolCalls: ToolCallSummary[], trace: Record<string, unknown>, fallbackPortal: string | undefined, now: string, options?: DataSourceOptions): DataSourceEntry[];
94
+ export declare function buildDataSourcesWith(toolCalls: ToolCallSummary[], trace: Record<string, unknown>, now: string, config: DataSourceBuildConfig): DataSourceEntry[];
@@ -2,17 +2,19 @@
2
2
  // spans plus the caller-supplied tool-call summary to produce one
3
3
  // `dataSources` entry per (source, datasetId) tuple. Relocated from
4
4
  // civic-ai-tools-website `src/lib/evidence/data-sources.ts:78–185` per the S2
5
- // brief §1, with two changes:
6
- // - The tool-name → source-id resolver is a CALLER-SUPPLIED input (the
7
- // app's MCP registry stays app-side); `civicToolSourceResolver` is the
8
- // exported civic default.
9
- // - The per-source coordinates (catalog types, endpoints) come from the
10
- // format-extension group's source registry instead of module constants —
11
- // which sources are dataset-keyed vs aggregate is registry-driven.
5
+ // brief §1.
6
+ //
7
+ // THE SEAM (civic-ai-tools#244 P2). This module imports nothing civic: the
8
+ // tool-name → source-id resolver, the source registry (catalog types,
9
+ // endpoints, the dataset-keyed vs aggregate split), the fallback source id
10
+ // and the dataset-keyed fact all arrive as parameters, every one of them
11
+ // required. The civic defaults are applied one layer up, in
12
+ // `src/civic/data-sources.ts`, which exports the package's `buildDataSources`,
13
+ // `resolveToolSource` and `DataSourceOptions` under their existing names and
14
+ // signatures.
12
15
  //
13
16
  // `DataSourceEntry` is produce-core's envelope input shape — the harness
14
17
  // populates it, never redefines it.
15
- import { CIVIC_SOURCE_REGISTRY, FALLBACK_SOURCE_ID, civicToolSourceResolver, isDatasetKeyedSource, } from "../format/sources.js";
16
18
  function getToolSpans(trace) {
17
19
  try {
18
20
  // Untyped walk over the caller's trace shape.
@@ -42,7 +44,7 @@ function spanAttr(span, key) {
42
44
  * flow. When the counts diverge, the static resolver still identifies the
43
45
  * source.
44
46
  */
45
- export function resolveToolSource(toolCall, span, resolver = civicToolSourceResolver, fallbackSourceId = FALLBACK_SOURCE_ID) {
47
+ export function resolveToolSourceWith(toolCall, span, resolver, fallbackSourceId) {
46
48
  return spanAttr(span, 'mcp.source')
47
49
  ?? resolver(toolCall.name)
48
50
  ?? fallbackSourceId;
@@ -69,17 +71,12 @@ export function resolveToolSource(toolCall, span, resolver = civicToolSourceReso
69
71
  * activities and in the caller's own `queries[]` — what it is not is an
70
72
  * assertion, inside signed bytes, that a source was reached at a timestamp.
71
73
  *
72
- * @param fallbackPortal DEPRECATED, and inert since 0.3.1: an entry states
73
- * the portal the call carried, never the run's. It stays third of five
74
- * positional parameters so existing callers keep compiling, and since 0.4.0
75
- * it also accepts `undefined`, which is what a caller that has stopped
76
- * consulting it should pass. Dropping it is a breaking change and waits for
77
- * a major.
74
+ * The run's portal is not a parameter: an entry states the portal the call
75
+ * carried, never the run's (the civic layer's `buildDataSources` keeps its
76
+ * inert `fallbackPortal` positional for existing callers).
78
77
  */
79
- export function buildDataSources(toolCalls, trace, fallbackPortal, now, options = {}) {
80
- const resolver = options.resolver ?? civicToolSourceResolver;
81
- const registry = options.registry ?? CIVIC_SOURCE_REGISTRY;
82
- const fallbackSourceId = options.fallbackSourceId ?? FALLBACK_SOURCE_ID;
78
+ export function buildDataSourcesWith(toolCalls, trace, now, config) {
79
+ const { resolver, registry, fallbackSourceId } = config;
83
80
  const toolSpans = getToolSpans(trace);
84
81
  const datasetKeyed = new Map();
85
82
  const aggregateAccessed = new Set();
@@ -87,12 +84,12 @@ export function buildDataSources(toolCalls, trace, fallbackPortal, now, options
87
84
  const tc = toolCalls[i];
88
85
  // A call the producer recorded as rejected reached no data, so it mints
89
86
  // no entry on either branch below. The walk keeps its index rather than
90
- // filtering the list: `resolveToolSource` pairs a call to `toolSpans[i]`,
87
+ // filtering the list: `resolveToolSourceWith` pairs a call to `toolSpans[i]`,
91
88
  // and a filtered list would shift every later call onto the wrong span.
92
89
  if (tc.failed)
93
90
  continue;
94
- const source = resolveToolSource(tc, toolSpans[i], resolver, fallbackSourceId);
95
- if (isDatasetKeyedSource(source, registry)) {
91
+ const source = resolveToolSourceWith(tc, toolSpans[i], resolver, fallbackSourceId);
92
+ if (config.isDatasetKeyed(source)) {
96
93
  const datasetId = tc.args.dataset_id;
97
94
  const portal = tc.args.portal;
98
95
  // An entry is minted only from what the call carried: a dataset id AND
@@ -1,6 +1,4 @@
1
1
  import { type ProvGraph, type ProvNode } from '@typedstandards/produce-core';
2
- import { type CivicVocabulary, type PlatformAgentConfig } from '../format/vocabulary.ts';
3
- import { type CivicSourceRegistry } from '../format/sources.ts';
4
2
  export type { ProvGraph, ProvNode };
5
3
  /** Package-level inputs to the graph build. */
6
4
  export interface ProvenanceInput {
@@ -27,41 +25,88 @@ export interface ProvenanceInput {
27
25
  * literal an excess-property error, so removal waits for a major. */
28
26
  portal?: string;
29
27
  }
30
- /** Instance configuration for the graph build (config-not-constants). */
31
- export interface ProvenanceConfig {
28
+ /** The id scheme and `@context` the builder emits under. Structural: the
29
+ * format group's vocabulary objects satisfy it, and this module never
30
+ * imports them. */
31
+ export interface ProvenanceVocabulary {
32
+ /** The graph's JSON-LD `@context`, in emission order. */
33
+ context(): Record<string, string>;
34
+ /** Package-scoped node id. */
35
+ urn(packageId: string, type: string, id: string): string;
36
+ /** Model-agent id. */
37
+ modelUrn(model: string): string;
38
+ /** MCP source-agent id. The builder encodes an UNKNOWN id with
39
+ * `encodeURIComponent` before passing it. */
40
+ sourceAgentUrn(sourceId: string): string;
41
+ /** Platform-agent id. */
42
+ platformUrn(platformId: string): string;
43
+ }
44
+ /** The property names the builder keys its domain-specific properties with,
45
+ * one per property. The builder spells none of them; the civic layer passes
46
+ * the format group's declared terms. */
47
+ export interface ProvenanceTerms {
48
+ readonly contentHash: string;
49
+ readonly serverUrl: string;
50
+ readonly sourceId: string;
51
+ readonly url: string;
52
+ readonly promptTokens: string;
53
+ readonly completionTokens: string;
54
+ readonly toolName: string;
55
+ readonly operationType: string;
56
+ readonly datasetId: string;
57
+ readonly portalDomain: string;
58
+ readonly datasetUrl: string;
59
+ readonly croissantMetadataUrl: string;
60
+ readonly responseRows: string;
61
+ readonly durationMs: string;
62
+ readonly failed: string;
63
+ readonly failureKind: string;
64
+ /** The wire name of a call whose exposed name is aliased (ADR-0033 §B). */
65
+ readonly upstreamToolName: string;
66
+ /** The alias of the source that answered a call. */
67
+ readonly sourceAlias: string;
68
+ /** The identity of the server that answered a call. */
69
+ readonly serverId: string;
70
+ }
71
+ /** One source the builder can name as an agent. Structural: a format-group
72
+ * registry entry satisfies it. */
73
+ export interface ProvenanceSourceInfo {
74
+ /** The agent's `dcterms:title`. */
75
+ agentTitle: string;
76
+ /** The agent's server URL. Absent means the graph states no address for
77
+ * the source (civic-ai-tools#205). */
78
+ serverUrl?: string;
79
+ }
80
+ /** Every input the builder reads, each one explicit. */
81
+ export interface ProvenanceBuildConfig {
32
82
  /** Platform-agent identity — the deployment publishing the record. */
33
- platformAgent: PlatformAgentConfig;
34
- /** Source registry supplying agent titles and MCP server URLs. Caller-
35
- * supplied configuration: an instance passes the addresses it is pointed
36
- * at, and an entry with no `serverUrl` makes its agent omit
37
- * `civic:serverUrl` (civic-ai-tools#205). */
38
- sourceRegistry: CivicSourceRegistry;
39
- /** Source id untagged tool spans fall back to. Default `socrata`
40
- * (pre-source-tagging captures were Socrata-only). */
41
- fallbackSourceId?: string;
42
- /** Source whose agent `civic:serverUrl` the trace's skill-fetch span URL
43
- * overrides when present (the skill is fetched from that source's MCP
44
- * server). Default `socrata`. */
45
- skillSourceId?: string;
46
- /** `dcterms:description` of the model agent. When unset, the model agent
47
- * carries no `dcterms:description` at all — the field is omitted from the
48
- * graph (honest omission), never filled with a fallback. */
83
+ platformAgent: {
84
+ id: string;
85
+ title: string;
86
+ url: string;
87
+ };
88
+ /** Source id → agent coordinates. Insertion order is agent order. */
89
+ sourceRegistry: Record<string, ProvenanceSourceInfo>;
90
+ /** Is this source dataset-keyed (its data responses carry the dataset id,
91
+ * portal and dataset URL)? Supplied by the caller, consistent with
92
+ * `sourceRegistry`. */
93
+ isDatasetKeyed: (sourceId: string) => boolean;
94
+ /** Tool-name → source-id resolver, consulted for a span that carries no
95
+ * `mcp.source` tag. */
96
+ resolver: (toolName: string) => string | undefined;
97
+ /** Source id for a span neither its tag nor the resolver identifies. */
98
+ fallbackSourceId: string;
99
+ /** Source whose agent server URL the trace's skill-fetch span URL
100
+ * overrides when present. */
101
+ skillSourceId: string;
102
+ /** `dcterms:description` of the model agent; omitted from the graph when
103
+ * unset, never filled with a fallback. */
49
104
  modelAgentDescription?: string;
50
- /** Vocabulary era to emit (spec Appendix J). Defaults to the settlement-era
51
- * `CIVIC_VOCABULARY` — the only value a new emission may use. Supply
52
- * `PRIOR_ERA_CIVIC_VOCABULARY` ONLY to reproduce or verify a record signed
53
- * before the 2026-08-19 settlement, whose identifiers are frozen under the
54
- * hash it was signed with. Unlike the other fields here this is not
55
- * deployment identity, so it is defaulted rather than required: emitting
56
- * the current vocabulary is never the silent-attribution hazard ADR-0024
57
- * guards against. */
58
- vocabulary?: CivicVocabulary;
105
+ /** Id scheme and `@context`. */
106
+ vocabulary: ProvenanceVocabulary;
107
+ /** Property names. */
108
+ terms: ProvenanceTerms;
59
109
  }
60
- /** The civicaitools.org reference deployment's values. Passed explicitly by
61
- * the reference app — never applied as a default, and never spread into
62
- * another instance's config, which would assert infrastructure that
63
- * instance doesn't run. */
64
- export declare const CIVICAITOOLS_PROVENANCE_CONFIG: ProvenanceConfig;
65
110
  /**
66
111
  * Build a W3C PROV-O JSON-LD graph from an OTel trace and package metadata.
67
112
  *
@@ -71,4 +116,4 @@ export declare const CIVICAITOOLS_PROVENANCE_CONFIG: ProvenanceConfig;
71
116
  * - Prompt, skill guidance, data responses, output → prov:Entity
72
117
  * - LLM model, MCP server, platform → prov:Agent
73
118
  */
74
- export declare function buildProvenanceGraph(trace: Record<string, unknown>, input: ProvenanceInput, config: ProvenanceConfig): ProvGraph;
119
+ export declare function buildProvenanceGraphWith(trace: Record<string, unknown>, input: ProvenanceInput, config: ProvenanceBuildConfig): ProvGraph;