@typedstandards/civic-typed-harness 0.4.1 → 0.6.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,163 @@
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.6.0 — 2026-09-26
7
+
8
+ The list is derived from
9
+ `git log 65e8a1d..<this release> -- packages/civic-typed-harness/` — `65e8a1d`
10
+ is the 0.5.0 release's last commit, its dating fix. No harness source changes
11
+ in that range.
12
+
13
+ - **Dependency floor raised:** `@typedstandards/produce-core` `^0.3.0 ||
14
+ ^0.4.0` → `^0.7.0`, and the `@typedstandards/verify-core` devDependency
15
+ `^0.9.0` → `^0.12.0`, the version produce-core 0.7.0 resolves. The two move
16
+ together so the install holds one copy of verify-core: with only the
17
+ devDependency moved, produce-core 0.3.0 kept verify-core 0.9.0 and the
18
+ harness tests imported a second copy. The raise went to `^0.6.0` /
19
+ `^0.11.0` first ([civic-ai-tools#232](https://github.com/npstorey/civic-ai-tools/issues/232))
20
+ and was not published at that range.
21
+ - **What the two releases bring.** verify-core 0.11.0 is the first release
22
+ that exports `KNOWN_TYPE_URIS`, which the hub's `check:verifier-subtype-set`
23
+ compares with the specification's §8.12.1 table. verify-core 0.12.0 (the
24
+ certificate-chain split, typedstandards#100) adds a member to
25
+ `ChainFailReason` and to `Rfc3161FailReason`; the harness reads neither.
26
+ produce-core 0.7.0 changes none of its own source.
27
+ - The lockfile resolves produce-core 0.7.0 and verify-core 0.12.0, one copy
28
+ each, and no other entry moves. The golden byte-compat suite passes
29
+ unchanged.
30
+ - A consumer that pins produce-core 0.3.x through 0.6.x no longer shares its
31
+ copy with this package: under 0.x semver `^0.7.0` excludes them.
32
+ - **README:** the runtime-dependency line named produce-core `^0.2.0`, a range
33
+ this package has not declared since 0.3.0. It names `^0.7.0`.
34
+
35
+ ## 0.5.0 — 2026-09-18
36
+
37
+ The list is derived from
38
+ `git log 33a3aff..<this release> -- packages/civic-typed-harness/` — `33a3aff`
39
+ is the 0.4.1 release bump — so it is the commit range rather than a
40
+ recollection.
41
+ Three commits landed after that bump and had no entry; this phase's two
42
+ changes join them.
43
+
44
+ **Together, the two changes below make a record name the servers the run
45
+ actually had, and say nothing where it does not know.** They are the harness
46
+ half of
47
+ [civic-ai-tools#205](https://github.com/npstorey/civic-ai-tools/issues/205)
48
+ and
49
+ [civic-ai-tools-website#449](https://github.com/npstorey/civic-ai-tools-website/issues/449);
50
+ the caller half (a registry built from the instance's own configuration, and
51
+ the packager passing the list) lands in the reference application.
52
+
53
+ ### The environment extension can name every server, not only the skill's
54
+
55
+ - **`buildDatHereEnvironment` accepts a list of MCP servers**, additively. Its
56
+ second parameter is now `string | readonly DatHereMcpServer[] | undefined`:
57
+ the bare string is the pre-0.5.0 shape — the trace's skill-fetch URL, the one
58
+ server the builder could name — and a list is emitted in the order the caller
59
+ gives, each entry carrying `name` only when the caller supplied one. Spec
60
+ §8.7.1 requirement 3 always specified `mcpServers` as "an array of objects
61
+ with `url` and optional `name`"; until now the harness could only ever put
62
+ one object in it.
63
+ - **The single-URL shape is byte-identical to before**, and a test pins those
64
+ exact bytes rather than deep-equality, because an already-signed datHere
65
+ package reproduces through this function. A one-entry list with no `name`
66
+ produces the same bytes as the string, which is the same statement from the
67
+ other side.
68
+ - **`DatHerePolicyInput` gains `mcpServers`**, consulted in preference to
69
+ `skillMcpServerUrl` and falling back to it, so `deriveDatHereEnvelopeFields`
70
+ carries the list through without any existing caller changing.
71
+ - **New export:** the `DatHereMcpServer` interface. An entry whose `url` is
72
+ empty contributes nothing — the same rule the single-URL shape always
73
+ applied: a server with no address is stated by absence, never by an empty
74
+ string.
75
+
76
+ ### A source agent names the configured address, or names no address at all
77
+
78
+ - **`CivicSourceInfo.serverUrl` is optional.** The address a run reached is the
79
+ operator's configuration, not a fact the harness knows. A caller passes a
80
+ registry whose entries carry the addresses that instance is pointed at.
81
+ - **`buildProvenanceGraph` omits `civic:serverUrl` when it has no address.**
82
+ Two shapes reach that: a registry entry that carries no `serverUrl`, and a
83
+ source id the registry does not carry at all. Both now emit an agent with no
84
+ `civic:serverUrl` key — the key's absence, not an empty string — so the
85
+ signed bytes say the address is unknown instead of naming a server the
86
+ analysis may never have contacted. When an address IS known the key keeps its
87
+ position in the emitted key order, which is the byte contract on the legacy
88
+ chain.
89
+ - **The source-id fallback is dropped.** An agent for a source outside the
90
+ registry used to carry `civic:serverUrl` equal to its own source id — a
91
+ string that was never an address. That is the asserted-default class
92
+ ([civic-ai-tools#129](https://github.com/npstorey/civic-ai-tools/issues/129)'s
93
+ family): a stand-in standing in a signed record for a fact the run had and
94
+ did not write.
95
+ - **`CIVIC_SOURCE_REGISTRY` keeps its three `serverUrl` values**, and the
96
+ comment now says what they are for: the reference deployment's own default
97
+ addresses for display, and — on the two aggregate sources — the companions of
98
+ `aggregatePortalUrl`. They are not what a record asserts about a run.
99
+ - **Type-level consequence, stated rather than glossed.** Widening
100
+ `serverUrl` to optional is additive for a caller that BUILDS a registry and
101
+ narrowing for one that READS `info.serverUrl` and expects a `string`. A
102
+ TypeScript consumer doing the latter has to handle `undefined`.
103
+
104
+ **Golden bytes.** Two of the three fixtures are untouched;
105
+ `__fixtures__/website-golden.json` and `__fixtures__/reference-golden.json` are
106
+ frozen reference captures and no byte of either is edited. The 2026-08-01
107
+ website capture carries one MCP agent for a source its registry does not know,
108
+ and that agent's `civic:serverUrl` is the dropped fallback, so the suites lift
109
+ the capture over exactly that one key — derived from the registry, with the
110
+ number of lifted nodes asserted, the same device already used for the two
111
+ Appendix J literals and for the synthetic prompt hash. The lifted graph is
112
+ inside the hashed bytes of the ninth golden-reproduction case, so
113
+ `__fixtures__/span-carrying-golden.json`'s two recorded hashes are regenerated,
114
+ intentionally and by that one dropped key: `envelopeHash` `eaa41755…b9b87d` →
115
+ `2a57c911…c0af89`, `contentHashSha256` `cd036569…0e0202` → `e14adecf…a6af5f`.
116
+ The fixture records the reason beside them. **A package signed before this
117
+ change whose graph carried the source-id fallback no longer reproduces at the
118
+ defaults** — that is the intended consequence of dropping an asserted default,
119
+ and it is named here so nobody reads a moved hash as an accident.
120
+
121
+ ### Already on `main` since 0.4.1, and unreleased
122
+
123
+ - **The test files type-check**
124
+ ([civic-ai-tools#197](https://github.com/npstorey/civic-ai-tools/issues/197),
125
+ `0a861b1`). `tsconfig.test.json` extends the build config, emits nothing, and
126
+ adds the Node types the suite needs; the package's `typecheck` script runs
127
+ both configs, and `src/the-suite-type-checks.test.ts` guards that every test
128
+ file is inside the checking config and none is inside the emitting one. The
129
+ build config now sets `types: []`, so shipped source still cannot reach a
130
+ Node global and type-check. No runtime export changes.
131
+ - **The `civic:` vocabulary is named, and a rejected call states its elapsed**
132
+ ([civic-ai-tools#199](https://github.com/npstorey/civic-ai-tools/issues/199)
133
+ §2, `e4e32e7`). **Fourteen new `CIVIC_TERM_*` exports** —
134
+ `CIVIC_TERM_CONTENT_HASH`, `CIVIC_TERM_SERVER_URL`, `CIVIC_TERM_SOURCE_ID`,
135
+ `CIVIC_TERM_URL`, `CIVIC_TERM_PROMPT_TOKENS`, `CIVIC_TERM_COMPLETION_TOKENS`,
136
+ `CIVIC_TERM_TOOL_NAME`, `CIVIC_TERM_OPERATION_TYPE`, `CIVIC_TERM_DATASET_ID`,
137
+ `CIVIC_TERM_PORTAL_DOMAIN`, `CIVIC_TERM_DATASET_URL`,
138
+ `CIVIC_TERM_CROISSANT_METADATA_URL`, `CIVIC_TERM_RESPONSE_ROWS`,
139
+ `CIVIC_TERM_DURATION_MS` — replacing inline literals at twenty sites in
140
+ `capture/provenance.ts`, with no byte moving (a computed key inserts in the
141
+ same position with the same value). `purity.test.ts`'s guard derives its
142
+ universe rather than listing literals. The same commit corrected two comments
143
+ that said a rejected span carries no `tool.duration_ms`: the reference
144
+ producer records it, so a rejected call's activity emits `civic:durationMs`
145
+ beside `civic:failed`, the duration in its 0.3.1 position and the two markers
146
+ appended after it.
147
+ - **A golden case that carries `mcp_tool_call` spans**
148
+ ([civic-ai-tools#199](https://github.com/npstorey/civic-ai-tools/issues/199)
149
+ §1, `92efbae`). `__fixtures__/span-carrying-golden.json` — a ninth
150
+ golden-reproduction case, in its own file so the verbatim upstream copy stays
151
+ a copy. The other eight carry zero spans of any kind, so until it existed the
152
+ byte-compat suite was green for everything the tool-span loop does. Test-only;
153
+ no export changes.
154
+
155
+ **Correction to the 0.4.0 entry.** That entry's type-level-gate bullet says
156
+ "`tsconfig.json` excludes `src/**/*.test.ts`, so the suite type-checks nothing
157
+ — a test can drive a field the type does not have and `npm run typecheck` stays
158
+ green." **That has been false since `0a861b1`.** The suite is type-checked by
159
+ `tsconfig.test.json`, `npm run typecheck` runs it, and a type error in a test
160
+ file fails that gate. The sentence is true of 0.4.0 as published and is left in
161
+ place as the record of it; this is the forward correction.
162
+
6
163
  ## 0.4.1 — 2026-09-12
7
164
 
8
165
  Patch: one existing export changes its output for one input shape, and no
package/README.md CHANGED
@@ -49,7 +49,7 @@ Likewise: `buildDatHereEnvironment` / `deriveDatHereEnvelopeFields` take `CIVICA
49
49
 
50
50
  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
51
 
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).
52
+ Runtime dependency: **`@typedstandards/produce-core` (`^0.7.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
53
 
54
54
  > **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
55
 
@@ -31,7 +31,10 @@ export interface ProvenanceInput {
31
31
  export interface ProvenanceConfig {
32
32
  /** Platform-agent identity — the deployment publishing the record. */
33
33
  platformAgent: PlatformAgentConfig;
34
- /** Source registry supplying agent titles and MCP server URLs. */
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). */
35
38
  sourceRegistry: CivicSourceRegistry;
36
39
  /** Source id untagged tool spans fall back to. Default `socrata`
37
40
  * (pre-source-tagging captures were Socrata-only). */
@@ -17,7 +17,7 @@
17
17
  // order is preserved throughout — the legacy hash chain's byte contract).
18
18
  import { makeProvGraph, makeEntityNode, makeActivityNode, makeAgentNode, provRef, provUsed, provWasGeneratedBy, provWasDerivedFrom, provWasAssociatedWith, xsdDateTime, } from '@typedstandards/produce-core';
19
19
  import { hash } from "./trace.js";
20
- import { CIVIC_VOCABULARY, CIVICAITOOLS_PLATFORM_AGENT, CIVIC_TERM_FAILED, CIVIC_TERM_FAILURE_KIND, } from "../format/vocabulary.js";
20
+ import { CIVIC_VOCABULARY, CIVICAITOOLS_PLATFORM_AGENT, CIVIC_TERM_COMPLETION_TOKENS, CIVIC_TERM_CONTENT_HASH, CIVIC_TERM_CROISSANT_METADATA_URL, CIVIC_TERM_DATASET_ID, CIVIC_TERM_DATASET_URL, CIVIC_TERM_DURATION_MS, CIVIC_TERM_FAILED, CIVIC_TERM_FAILURE_KIND, CIVIC_TERM_OPERATION_TYPE, CIVIC_TERM_PORTAL_DOMAIN, CIVIC_TERM_PROMPT_TOKENS, CIVIC_TERM_RESPONSE_ROWS, CIVIC_TERM_SERVER_URL, CIVIC_TERM_SOURCE_ID, CIVIC_TERM_TOOL_NAME, CIVIC_TERM_URL, } from "../format/vocabulary.js";
21
21
  import { CIVIC_SOURCE_REGISTRY, FALLBACK_SOURCE_ID, isDatasetKeyedSource, } from "../format/sources.js";
22
22
  /** The civicaitools.org reference deployment's values. Passed explicitly by
23
23
  * the reference app — never applied as a default, and never spread into
@@ -88,7 +88,7 @@ export function buildProvenanceGraph(trace, input, config) {
88
88
  // --- Entities ---
89
89
  // User prompt
90
90
  graph.push(makeEntityNode(vocab.urn(packageId, 'prompt', promptHash), {
91
- 'civic:contentHash': `sha256:${promptHash}`,
91
+ [CIVIC_TERM_CONTENT_HASH]: `sha256:${promptHash}`,
92
92
  'dcterms:description': 'User query prompt',
93
93
  ...(promptText ? { 'prov:value': promptText } : {}),
94
94
  }));
@@ -101,13 +101,13 @@ export function buildProvenanceGraph(trace, input, config) {
101
101
  const skillServerUrl = skillSpan ? getAttr(skillSpan.attributes, 'skill.mcp_server_url') : undefined;
102
102
  if (skillHash) {
103
103
  graph.push(makeEntityNode(vocab.urn(packageId, 'skill', skillHash), {
104
- 'civic:contentHash': `sha256:${skillHash}`,
104
+ [CIVIC_TERM_CONTENT_HASH]: `sha256:${skillHash}`,
105
105
  'dcterms:description': 'Composed MCP skill guidance (system prompt)',
106
106
  }, ['prov:Plan']));
107
107
  }
108
108
  // Final output
109
109
  graph.push(makeEntityNode(vocab.urn(packageId, 'output', outputHash), {
110
- 'civic:contentHash': `sha256:${outputHash}`,
110
+ [CIVIC_TERM_CONTENT_HASH]: `sha256:${outputHash}`,
111
111
  'dcterms:description': 'AI-generated analysis output',
112
112
  }));
113
113
  // --- Agents ---
@@ -127,6 +127,13 @@ export function buildProvenanceGraph(trace, input, config) {
127
127
  // configured fallback source (socrata — the only source at the time) for
128
128
  // backwards compatibility. Agent coordinates come from the source registry;
129
129
  // the skill-fetch span URL overrides the skill source's server URL.
130
+ //
131
+ // THE ADDRESS IS THE OPERATOR'S, OR IT IS ABSENT (civic-ai-tools#205, 0.5.0).
132
+ // `serverUrl` is optional on a registry entry and there is no stand-in for a
133
+ // source whose address this run does not know: a source the registry does
134
+ // not carry, and a registry entry with no address, both emit an agent with
135
+ // no `civic:serverUrl` key at all. The signed bytes then say the address is
136
+ // unknown instead of naming a server the analysis may never have contacted.
130
137
  const toolSpans = spans.filter(s => s.name === 'mcp_tool_call');
131
138
  const sourceAgentMap = {};
132
139
  for (const [id, info] of Object.entries(registry)) {
@@ -145,12 +152,13 @@ export function buildProvenanceGraph(trace, input, config) {
145
152
  const meta = sourceAgentMap[sourceId] ?? {
146
153
  urn: vocab.sourceAgentUrn(encodeURIComponent(sourceId)),
147
154
  title: `${sourceId} MCP Server`,
148
- serverUrl: sourceId,
149
155
  };
150
156
  graph.push(makeAgentNode(meta.urn, {
151
157
  'dcterms:title': meta.title,
152
- 'civic:serverUrl': meta.serverUrl,
153
- 'civic:sourceId': sourceId,
158
+ // Conditional spread in place, so a known address keeps its
159
+ // position in the key order the legacy chain hashes.
160
+ ...(meta.serverUrl ? { [CIVIC_TERM_SERVER_URL]: meta.serverUrl } : {}),
161
+ [CIVIC_TERM_SOURCE_ID]: sourceId,
154
162
  }, ['prov:SoftwareAgent']));
155
163
  }
156
164
  // Resolve a source id to its agent URN, falling back to the configured
@@ -165,7 +173,7 @@ export function buildProvenanceGraph(trace, input, config) {
165
173
  // Platform
166
174
  graph.push(makeAgentNode(vocab.platformUrn(config.platformAgent.id), {
167
175
  'dcterms:title': config.platformAgent.title,
168
- 'civic:url': config.platformAgent.url,
176
+ [CIVIC_TERM_URL]: config.platformAgent.url,
169
177
  }, ['prov:SoftwareAgent']));
170
178
  // --- Activities ---
171
179
  // LLM inference spans
@@ -190,8 +198,8 @@ export function buildProvenanceGraph(trace, input, config) {
190
198
  ...(span.endTimeUnixNano
191
199
  ? { 'prov:endedAtTime': xsdDateTime(nanoToIso(span.endTimeUnixNano)) }
192
200
  : {}),
193
- ...(promptTokens ? { 'civic:promptTokens': Number(promptTokens) } : {}),
194
- ...(completionTokens ? { 'civic:completionTokens': Number(completionTokens) } : {}),
201
+ ...(promptTokens ? { [CIVIC_TERM_PROMPT_TOKENS]: Number(promptTokens) } : {}),
202
+ ...(completionTokens ? { [CIVIC_TERM_COMPLETION_TOKENS]: Number(completionTokens) } : {}),
195
203
  }));
196
204
  }
197
205
  // MCP tool call spans.
@@ -233,10 +241,10 @@ export function buildProvenanceGraph(trace, input, config) {
233
241
  .sort((a, b) => Number(b.endTimeUnixNano || '0') - Number(a.endTimeUnixNano || '0'))[0];
234
242
  const queryUrn = vocab.urn(packageId, 'query', queryHash);
235
243
  graph.push(makeEntityNode(queryUrn, {
236
- 'civic:contentHash': `sha256:${queryHash}`,
244
+ [CIVIC_TERM_CONTENT_HASH]: `sha256:${queryHash}`,
237
245
  // Omitted — not placeholdered — when the span named no tool.
238
- ...(toolName ? { 'civic:toolName': toolName } : {}),
239
- 'civic:operationType': opType,
246
+ ...(toolName ? { [CIVIC_TERM_TOOL_NAME]: toolName } : {}),
247
+ [CIVIC_TERM_OPERATION_TYPE]: opType,
240
248
  'dcterms:description': `MCP tool arguments (${opType})`,
241
249
  ...(precedingInference
242
250
  ? provWasGeneratedBy(vocab.urn(packageId, 'inference', precedingInference.spanId))
@@ -264,9 +272,9 @@ export function buildProvenanceGraph(trace, input, config) {
264
272
  : `Data response from ${sourceAgentMap[toolSource]?.title || toolSource}`;
265
273
  const responseRows = getAttr(span.attributes, 'tool.response_rows');
266
274
  graph.push(makeEntityNode(dataUrn, {
267
- 'civic:contentHash': `sha256:${responseHash}`,
275
+ [CIVIC_TERM_CONTENT_HASH]: `sha256:${responseHash}`,
268
276
  'dcterms:description': description,
269
- 'civic:sourceId': toolSource,
277
+ [CIVIC_TERM_SOURCE_ID]: toolSource,
270
278
  ...provWasGeneratedBy(toolCallUrn),
271
279
  // Croissant 1.1 placeholder — only meaningful for dataset-keyed
272
280
  // sources today. The dataset id is stated whenever the span carried
@@ -274,17 +282,17 @@ export function buildProvenanceGraph(trace, input, config) {
274
282
  // the portal as well. Key order is the byte contract.
275
283
  ...(toolSourceDatasetKeyed && datasetId
276
284
  ? {
277
- 'civic:datasetId': datasetId,
285
+ [CIVIC_TERM_DATASET_ID]: datasetId,
278
286
  ...(portalDomain
279
287
  ? {
280
- 'civic:portalDomain': portalDomain,
281
- 'civic:datasetUrl': `https://${portalDomain}/d/${datasetId}`,
288
+ [CIVIC_TERM_PORTAL_DOMAIN]: portalDomain,
289
+ [CIVIC_TERM_DATASET_URL]: `https://${portalDomain}/d/${datasetId}`,
282
290
  }
283
291
  : {}),
284
- 'civic:croissantMetadataUrl': null, // hook for future Croissant integration
292
+ [CIVIC_TERM_CROISSANT_METADATA_URL]: null, // hook for future Croissant integration
285
293
  }
286
294
  : {}),
287
- ...(responseRows ? { 'civic:responseRows': Number(responseRows) } : {}),
295
+ ...(responseRows ? { [CIVIC_TERM_RESPONSE_ROWS]: Number(responseRows) } : {}),
288
296
  }));
289
297
  }
290
298
  // Tool call activity — associated with the MCP source agent that handled
@@ -317,7 +325,7 @@ export function buildProvenanceGraph(trace, input, config) {
317
325
  'dcterms:description': toolName
318
326
  ? `MCP tool call: ${toolName} (${opType})`
319
327
  : `MCP tool call (${opType})`,
320
- 'civic:sourceId': toolSource,
328
+ [CIVIC_TERM_SOURCE_ID]: toolSource,
321
329
  ...provUsed([queryUrn]),
322
330
  ...provWasAssociatedWith(toolAgentUrn),
323
331
  ...(span.startTimeUnixNano
@@ -326,16 +334,25 @@ export function buildProvenanceGraph(trace, input, config) {
326
334
  ...(span.endTimeUnixNano
327
335
  ? { 'prov:endedAtTime': xsdDateTime(nanoToIso(span.endTimeUnixNano)) }
328
336
  : {}),
329
- ...(durationMs ? { 'civic:durationMs': Number(durationMs) } : {}),
337
+ ...(durationMs ? { [CIVIC_TERM_DURATION_MS]: Number(durationMs) } : {}),
330
338
  // Appended LAST and spread conditionally, exactly as `civic:durationMs`
331
339
  // above is: a span that recorded no rejection yields the key list it
332
340
  // yielded at 0.3.1, in the same order, and property insertion order is
333
341
  // the legacy chain's byte contract. `false` is never emitted — a
334
342
  // producer that stated "not failed" and one that stated nothing must
335
343
  // read the same, which is what makes a marker that IS present mean
336
- // something. A rejected span carries no `tool.duration_ms` from the
337
- // reference producer today (civic-ai-tools-website#413), so
338
- // `civic:durationMs` simply does not fire beside these two.
344
+ // something.
345
+ //
346
+ // A REJECTED SPAN NOW CARRIES A DURATION. Until
347
+ // civic-ai-tools-website#413 shipped (Wave N11 P4, merged `ec7f173`)
348
+ // the reference producer measured a rejected call's elapsed and
349
+ // discarded it, so this comment said `civic:durationMs` "simply does
350
+ // not fire beside these two". It fires: the producer writes
351
+ // `tool.duration_ms` on the rejection path, and the conditional above
352
+ // states it — BEFORE the two markers, in the position it has held
353
+ // since 0.3.1, because that conditional is unchanged and this one is
354
+ // still appended after it. `capture/provenance.test.ts` drives that
355
+ // combination and pins the whole key list for it.
339
356
  ...(failed === true ? { [CIVIC_TERM_FAILED]: true } : {}),
340
357
  ...(failureKind ? { [CIVIC_TERM_FAILURE_KIND]: failureKind } : {}),
341
358
  }));
@@ -39,18 +39,37 @@ export declare function selectContentCanonicalization(contentProfile?: string):
39
39
  * value to pass as the envelope's `summary` field, or `undefined` to omit.
40
40
  */
41
41
  export declare function deriveSummaryEmission(contentProfile?: string, summary?: string): string | undefined;
42
+ /** One MCP server the run had available, as the environment extension carries
43
+ * it — spec §8.7.1 requirement 3: an object with `url` and optional `name`.
44
+ * The CALLER supplies the order and the names; the harness neither sorts nor
45
+ * invents them. */
46
+ export interface DatHereMcpServer {
47
+ /** The server's configured address. */
48
+ url: string;
49
+ /** Optional label for the server (the reference caller passes the source
50
+ * id). Omitted from the emitted object when absent, so a nameless entry
51
+ * stays byte-identical to the pre-0.5.0 single-server shape. */
52
+ name?: string;
53
+ }
42
54
  /**
43
55
  * Build the `org.civicaitools.environment` extension content for a datHere
44
56
  * package. Per OES §9.1.1 requirement 3 the extension MUST carry
45
57
  * `modelVersion`, `temperature`, `mcpServers`, `toolDefinitions`, `host`.
46
58
  *
59
+ * `mcpServers` accepts EITHER shape (additive since 0.5.0,
60
+ * civic-ai-tools-website#449): a single URL string — the trace's skill-fetch
61
+ * span URL, the only server the pre-0.5.0 builder could name — or the list of
62
+ * servers the run actually had, in the caller's order, each with an optional
63
+ * `name`. The single-URL shape produces byte-identical output to before, so
64
+ * an already-signed package still reproduces.
65
+ *
47
66
  * Prototype limitations carried over from the reference implementation
48
67
  * (known gaps, tightened in follow-up work): `temperature` placeholder `0`,
49
- * `toolDefinitions` placeholder `[]`; `mcpServers` derives from the trace's
50
- * skill-fetch span URL (the analysis's primary MCP server). Fields captured
51
- * honestly: `modelVersion` and `host`.
68
+ * `toolDefinitions` placeholder `[]`. Fields captured honestly:
69
+ * `modelVersion`, `host`, and — when the caller passes the list — the servers
70
+ * the run had.
52
71
  */
53
- export declare function buildDatHereEnvironment(model: string, skillMcpServerUrl: string | undefined, config: DatHereEnvironmentConfig): Record<string, unknown>;
72
+ export declare function buildDatHereEnvironment(model: string, mcpServers: string | readonly DatHereMcpServer[] | undefined, config: DatHereEnvironmentConfig): Record<string, unknown>;
54
73
  /** Input to the composite datHere-policy derivation. */
55
74
  export interface DatHerePolicyInput {
56
75
  /** Model identifier (becomes `environment.modelVersion`). */
@@ -62,8 +81,15 @@ export interface DatHerePolicyInput {
62
81
  /** Candidate summary text (emitted only under the datHere profile). */
63
82
  summary?: string;
64
83
  /** MCP server URL from the trace's skill-fetch span (capture-side
65
- * extraction — see src/capture/skill-metadata.ts). */
84
+ * extraction — see src/capture/skill-metadata.ts). Consulted only when
85
+ * `mcpServers` is absent, so the pre-0.5.0 call shape keeps producing
86
+ * byte-identical envelopes. */
66
87
  skillMcpServerUrl?: string;
88
+ /** Every MCP server the run had available, in the order the caller wants
89
+ * them emitted, each with an optional `name`
90
+ * (civic-ai-tools-website#449). When supplied this REPLACES
91
+ * `skillMcpServerUrl` as the source of `environment.mcpServers`. */
92
+ mcpServers?: readonly DatHereMcpServer[];
67
93
  /** Caller-supplied extensions to layer the environment extension onto. */
68
94
  extensions?: Record<string, unknown>;
69
95
  }
@@ -69,26 +69,50 @@ export function deriveSummaryEmission(contentProfile, summary) {
69
69
  ? summary
70
70
  : undefined;
71
71
  }
72
+ /** Normalize the two accepted `mcpServers` inputs to the emitted array.
73
+ *
74
+ * A bare string is the pre-0.5.0 shape — the trace's skill-fetch URL — and
75
+ * yields at most the one `{ url }` entry, exactly as before. A list is
76
+ * emitted in the order given, each entry carrying `name` only when the
77
+ * caller supplied one. An entry with an empty `url` contributes nothing,
78
+ * which is the same rule the single-URL shape always applied: a server with
79
+ * no address is stated by absence, never by an empty string. */
80
+ function normalizeMcpServers(servers) {
81
+ if (servers === undefined)
82
+ return [];
83
+ if (typeof servers === 'string') {
84
+ return servers ? [{ url: servers }] : [];
85
+ }
86
+ return servers
87
+ .filter((server) => Boolean(server.url))
88
+ .map((server) => ({
89
+ url: server.url,
90
+ ...(server.name ? { name: server.name } : {}),
91
+ }));
92
+ }
72
93
  /**
73
94
  * Build the `org.civicaitools.environment` extension content for a datHere
74
95
  * package. Per OES §9.1.1 requirement 3 the extension MUST carry
75
96
  * `modelVersion`, `temperature`, `mcpServers`, `toolDefinitions`, `host`.
76
97
  *
98
+ * `mcpServers` accepts EITHER shape (additive since 0.5.0,
99
+ * civic-ai-tools-website#449): a single URL string — the trace's skill-fetch
100
+ * span URL, the only server the pre-0.5.0 builder could name — or the list of
101
+ * servers the run actually had, in the caller's order, each with an optional
102
+ * `name`. The single-URL shape produces byte-identical output to before, so
103
+ * an already-signed package still reproduces.
104
+ *
77
105
  * Prototype limitations carried over from the reference implementation
78
106
  * (known gaps, tightened in follow-up work): `temperature` placeholder `0`,
79
- * `toolDefinitions` placeholder `[]`; `mcpServers` derives from the trace's
80
- * skill-fetch span URL (the analysis's primary MCP server). Fields captured
81
- * honestly: `modelVersion` and `host`.
107
+ * `toolDefinitions` placeholder `[]`. Fields captured honestly:
108
+ * `modelVersion`, `host`, and — when the caller passes the list — the servers
109
+ * the run had.
82
110
  */
83
- export function buildDatHereEnvironment(model, skillMcpServerUrl, config) {
84
- const mcpServers = [];
85
- if (skillMcpServerUrl) {
86
- mcpServers.push({ url: skillMcpServerUrl });
87
- }
111
+ export function buildDatHereEnvironment(model, mcpServers, config) {
88
112
  return {
89
113
  modelVersion: model,
90
114
  temperature: 0,
91
- mcpServers,
115
+ mcpServers: normalizeMcpServers(mcpServers),
92
116
  toolDefinitions: [],
93
117
  host: config.host,
94
118
  };
@@ -102,7 +126,10 @@ export function buildDatHereEnvironment(model, skillMcpServerUrl, config) {
102
126
  export function deriveDatHereEnvelopeFields(input, config) {
103
127
  const extensions = { ...(input.extensions ?? {}) };
104
128
  if (input.contentProfile === DATHERE_CONTENT_PROFILE) {
105
- extensions[ENVIRONMENT_EXTENSION_KEY] = buildDatHereEnvironment(input.model, input.skillMcpServerUrl, config);
129
+ extensions[ENVIRONMENT_EXTENSION_KEY] = buildDatHereEnvironment(input.model,
130
+ // The list when the caller has one, the single skill-fetch URL
131
+ // otherwise — an input carrying neither still emits `mcpServers: []`.
132
+ input.mcpServers ?? input.skillMcpServerUrl, config);
106
133
  }
107
134
  return {
108
135
  producerProfile: deriveProducerProfile(input.producerProfile, input.contentProfile),
@@ -5,8 +5,18 @@ export interface CivicSourceInfo {
5
5
  displayName: string;
6
6
  /** PROV agent `dcterms:title`, e.g. "Socrata MCP Server". */
7
7
  agentTitle: string;
8
- /** MCP endpoint URL — the PROV agent's `civic:serverUrl`. */
9
- serverUrl: string;
8
+ /**
9
+ * MCP endpoint URL — the PROV agent's `civic:serverUrl`, and this source's
10
+ * display address.
11
+ *
12
+ * OPTIONAL since 0.5.0 (civic-ai-tools#205). The address a run reached is
13
+ * the OPERATOR's configuration, not a fact the harness knows, so a caller
14
+ * passes a registry whose entries carry the addresses that instance is
15
+ * pointed at. An entry that carries no address makes the provenance graph
16
+ * OMIT `civic:serverUrl` from that source's agent entirely, rather than
17
+ * assert a stand-in: honest absence over an asserted default.
18
+ */
19
+ serverUrl?: string;
10
20
  /** `dataSources[].catalogType` value for entries from this source. */
11
21
  catalogType: string;
12
22
  /**
@@ -22,7 +32,14 @@ export interface CivicSourceInfo {
22
32
  * order for aggregate `dataSources` entries. */
23
33
  export type CivicSourceRegistry = Record<string, CivicSourceInfo>;
24
34
  /** The civic reference registry — the three demo sources. Passed explicitly
25
- * by the reference app — never applied as a default. */
35
+ * by the reference app — never applied as a default.
36
+ *
37
+ * THE `serverUrl` VALUES BELOW ARE THE REFERENCE DEPLOYMENT'S OWN, and since
38
+ * 0.5.0 (civic-ai-tools#205) that is the whole of their role: a default
39
+ * display address, and — on the two aggregate sources — the companion of
40
+ * `aggregatePortalUrl`. They are not what a record asserts about a run. An
41
+ * instance pointed at other addresses passes a registry carrying those; an
42
+ * entry with no address emits no `civic:serverUrl` at all. */
26
43
  export declare const CIVIC_SOURCE_REGISTRY: CivicSourceRegistry;
27
44
  /** The source id untagged/pre-M9.1 captures fall back to. Socrata was the
28
45
  * only source when those packages were written. */
@@ -13,7 +13,14 @@
13
13
  // demo map is exported as `CIVIC_SOURCE_REGISTRY` for explicit use, never
14
14
  // applied as a default.
15
15
  /** The civic reference registry — the three demo sources. Passed explicitly
16
- * by the reference app — never applied as a default. */
16
+ * by the reference app — never applied as a default.
17
+ *
18
+ * THE `serverUrl` VALUES BELOW ARE THE REFERENCE DEPLOYMENT'S OWN, and since
19
+ * 0.5.0 (civic-ai-tools#205) that is the whole of their role: a default
20
+ * display address, and — on the two aggregate sources — the companion of
21
+ * `aggregatePortalUrl`. They are not what a record asserts about a run. An
22
+ * instance pointed at other addresses passes a registry carrying those; an
23
+ * entry with no address emits no `civic:serverUrl` at all. */
17
24
  export const CIVIC_SOURCE_REGISTRY = {
18
25
  socrata: {
19
26
  displayName: 'Socrata',
@@ -35,6 +35,83 @@ export declare const CIVIC_TERM_FAILED = "civic:failed";
35
35
  * wider vocabulary widens this field rather than putting prose in it.
36
36
  */
37
37
  export declare const CIVIC_TERM_FAILURE_KIND = "civic:failureKind";
38
+ /**
39
+ * `civic:contentHash` — the SHA-256 of the bytes an entity stands for, as
40
+ * `sha256:<hex>`. Emitted on the prompt, skill, output, tool-argument and
41
+ * data-response entities: a reader hashes the content it holds and compares.
42
+ */
43
+ export declare const CIVIC_TERM_CONTENT_HASH = "civic:contentHash";
44
+ /**
45
+ * `civic:serverUrl` — the MCP server URL an source agent answered from, as
46
+ * the deployment's source registry states it (or, for the skill source, as
47
+ * the trace's skill-fetch span carried it).
48
+ */
49
+ export declare const CIVIC_TERM_SERVER_URL = "civic:serverUrl";
50
+ /**
51
+ * `civic:sourceId` — the stable id of the source that answered a call, on
52
+ * both the tool-call activity and the data-response entity. It is the
53
+ * registry key, not a display name: a reader resolves it, and the graph never
54
+ * spells the reader-facing name.
55
+ */
56
+ export declare const CIVIC_TERM_SOURCE_ID = "civic:sourceId";
57
+ /**
58
+ * `civic:url` — the public URL of the platform agent that published the
59
+ * record (the deployment's own address, a typed config input).
60
+ */
61
+ export declare const CIVIC_TERM_URL = "civic:url";
62
+ /** `civic:promptTokens` — prompt tokens the span reported for one inference.
63
+ * Emitted only when the span carried the count; never zero-filled. */
64
+ export declare const CIVIC_TERM_PROMPT_TOKENS = "civic:promptTokens";
65
+ /** `civic:completionTokens` — completion tokens the span reported for one
66
+ * inference. Emitted only when the span carried the count. */
67
+ export declare const CIVIC_TERM_COMPLETION_TOKENS = "civic:completionTokens";
68
+ /**
69
+ * `civic:toolName` — the tool a call invoked, verbatim from the span.
70
+ * Omitted, never placeholdered, when the span named no tool: the graph states
71
+ * absence as absence.
72
+ */
73
+ export declare const CIVIC_TERM_TOOL_NAME = "civic:toolName";
74
+ /**
75
+ * `civic:operationType` — the producer's classification of what a call did
76
+ * (`query`, `metadata`, `unknown`, …). An open string the graph states
77
+ * verbatim and never re-derives.
78
+ */
79
+ export declare const CIVIC_TERM_OPERATION_TYPE = "civic:operationType";
80
+ /**
81
+ * `civic:datasetId` — the dataset a data response came from, stated whenever
82
+ * the span carried one and the source is dataset-keyed.
83
+ */
84
+ export declare const CIVIC_TERM_DATASET_ID = "civic:datasetId";
85
+ /**
86
+ * `civic:portalDomain` — the portal host a tool span carried. Never the run's
87
+ * selected portal: a call that addressed no portal yields a response
88
+ * attributed to none.
89
+ */
90
+ export declare const CIVIC_TERM_PORTAL_DOMAIN = "civic:portalDomain";
91
+ /**
92
+ * `civic:datasetUrl` — the canonical dataset URL, minted only when the span
93
+ * carried BOTH the portal host and the dataset id (a URL needs a host the
94
+ * span actually stated).
95
+ */
96
+ export declare const CIVIC_TERM_DATASET_URL = "civic:datasetUrl";
97
+ /**
98
+ * `civic:croissantMetadataUrl` — the Croissant 1.1 metadata URL for a
99
+ * dataset-keyed data response. Emitted as an explicit `null` placeholder for
100
+ * a future integration; the key is inside signed bytes, so it is stated here
101
+ * rather than left as an undocumented literal.
102
+ */
103
+ export declare const CIVIC_TERM_CROISSANT_METADATA_URL = "civic:croissantMetadataUrl";
104
+ /** `civic:responseRows` — the row count a tool response carried, when the
105
+ * span reported one. */
106
+ export declare const CIVIC_TERM_RESPONSE_ROWS = "civic:responseRows";
107
+ /**
108
+ * `civic:durationMs` — the elapsed the producer measured for one tool call.
109
+ * Emitted whenever the span carried it, on a call that answered and on one
110
+ * the source REFUSED alike: since civic-ai-tools-website#413 the reference
111
+ * producer records the elapsed on its rejection path too, so this term and
112
+ * {@link CIVIC_TERM_FAILED} appear together on the same activity.
113
+ */
114
+ export declare const CIVIC_TERM_DURATION_MS = "civic:durationMs";
38
115
  /**
39
116
  * One era of the civic vocabulary: the two literals plus the emitters bound
40
117
  * to them. The vocabulary is not deployment configuration (a deployment does
@@ -37,16 +37,25 @@ export const PRIOR_ERA_CIVIC_URN_PREFIX = 'urn:civic-evidence';
37
37
  // A `civic:` property name is vocabulary as much as the namespace it hangs
38
38
  // under: it is the word a reader of a signed graph interprets, so it is
39
39
  // declared here and imported by the builder that emits it, never spelled as a
40
- // literal inside capture/ (`purity.test.ts` enforces that for the terms below).
41
- // Both terms are era-independent — the settlement moved the NAMESPACE, not the
42
- // property names — so they carry no era qualifier and are minted for the first
43
- // time after it.
40
+ // literal inside capture/. Every term is era-independent — the 2026-08-19
41
+ // settlement moved the NAMESPACE, not the property names — so none carries an
42
+ // era qualifier.
44
43
  //
45
- // The settlement-era property names that predate Wave N10 (`civic:sourceId`,
46
- // `civic:durationMs`, `civic:datasetId` and their siblings) are still inline
47
- // literals in the capture-side builder. That is a real inconsistency, recorded
48
- // rather than fixed in passing: moving them is a byte-sensitive change across
49
- // every golden fixture and belongs to a phase of its own.
44
+ // COMPLETE, AND GUARDED AS COMPLETE (civic-ai-tools#199 §2). Until this wave
45
+ // two terms were declared here and the other fourteen were spelled inline in
46
+ // capture/provenance.ts and declared nowhere, which the note that stood in
47
+ // this place recorded as "a real inconsistency ... a phase of its own". This
48
+ // is that phase. `purity.test.ts` no longer holds a list of the terms it
49
+ // knows about — it derives the universe from both ends: no string literal
50
+ // anywhere in capture/ may be a `civic:` term, and every `civic:` key a
51
+ // DRIVEN graph emits must be the value of a constant declared here. A term
52
+ // invented inline tomorrow fails both legs without anyone editing a list.
53
+ //
54
+ // NO BYTE MOVES. Naming a key changes no emitted byte: a computed key
55
+ // (`{ [CIVIC_TERM_SOURCE_ID]: v }`) inserts in the same position with the same
56
+ // value as the literal it replaces, and property insertion order is the legacy
57
+ // chain's byte contract. Both golden suites and the span-carrying golden case
58
+ // added in the same phase measure that rather than assume it.
50
59
  /**
51
60
  * `civic:failed` — a tool-call activity whose call the SOURCE REFUSED.
52
61
  *
@@ -71,6 +80,83 @@ export const CIVIC_TERM_FAILED = 'civic:failed';
71
80
  * wider vocabulary widens this field rather than putting prose in it.
72
81
  */
73
82
  export const CIVIC_TERM_FAILURE_KIND = 'civic:failureKind';
83
+ /**
84
+ * `civic:contentHash` — the SHA-256 of the bytes an entity stands for, as
85
+ * `sha256:<hex>`. Emitted on the prompt, skill, output, tool-argument and
86
+ * data-response entities: a reader hashes the content it holds and compares.
87
+ */
88
+ export const CIVIC_TERM_CONTENT_HASH = 'civic:contentHash';
89
+ /**
90
+ * `civic:serverUrl` — the MCP server URL an source agent answered from, as
91
+ * the deployment's source registry states it (or, for the skill source, as
92
+ * the trace's skill-fetch span carried it).
93
+ */
94
+ export const CIVIC_TERM_SERVER_URL = 'civic:serverUrl';
95
+ /**
96
+ * `civic:sourceId` — the stable id of the source that answered a call, on
97
+ * both the tool-call activity and the data-response entity. It is the
98
+ * registry key, not a display name: a reader resolves it, and the graph never
99
+ * spells the reader-facing name.
100
+ */
101
+ export const CIVIC_TERM_SOURCE_ID = 'civic:sourceId';
102
+ /**
103
+ * `civic:url` — the public URL of the platform agent that published the
104
+ * record (the deployment's own address, a typed config input).
105
+ */
106
+ export const CIVIC_TERM_URL = 'civic:url';
107
+ /** `civic:promptTokens` — prompt tokens the span reported for one inference.
108
+ * Emitted only when the span carried the count; never zero-filled. */
109
+ export const CIVIC_TERM_PROMPT_TOKENS = 'civic:promptTokens';
110
+ /** `civic:completionTokens` — completion tokens the span reported for one
111
+ * inference. Emitted only when the span carried the count. */
112
+ export const CIVIC_TERM_COMPLETION_TOKENS = 'civic:completionTokens';
113
+ /**
114
+ * `civic:toolName` — the tool a call invoked, verbatim from the span.
115
+ * Omitted, never placeholdered, when the span named no tool: the graph states
116
+ * absence as absence.
117
+ */
118
+ export const CIVIC_TERM_TOOL_NAME = 'civic:toolName';
119
+ /**
120
+ * `civic:operationType` — the producer's classification of what a call did
121
+ * (`query`, `metadata`, `unknown`, …). An open string the graph states
122
+ * verbatim and never re-derives.
123
+ */
124
+ export const CIVIC_TERM_OPERATION_TYPE = 'civic:operationType';
125
+ /**
126
+ * `civic:datasetId` — the dataset a data response came from, stated whenever
127
+ * the span carried one and the source is dataset-keyed.
128
+ */
129
+ export const CIVIC_TERM_DATASET_ID = 'civic:datasetId';
130
+ /**
131
+ * `civic:portalDomain` — the portal host a tool span carried. Never the run's
132
+ * selected portal: a call that addressed no portal yields a response
133
+ * attributed to none.
134
+ */
135
+ export const CIVIC_TERM_PORTAL_DOMAIN = 'civic:portalDomain';
136
+ /**
137
+ * `civic:datasetUrl` — the canonical dataset URL, minted only when the span
138
+ * carried BOTH the portal host and the dataset id (a URL needs a host the
139
+ * span actually stated).
140
+ */
141
+ export const CIVIC_TERM_DATASET_URL = 'civic:datasetUrl';
142
+ /**
143
+ * `civic:croissantMetadataUrl` — the Croissant 1.1 metadata URL for a
144
+ * dataset-keyed data response. Emitted as an explicit `null` placeholder for
145
+ * a future integration; the key is inside signed bytes, so it is stated here
146
+ * rather than left as an undocumented literal.
147
+ */
148
+ export const CIVIC_TERM_CROISSANT_METADATA_URL = 'civic:croissantMetadataUrl';
149
+ /** `civic:responseRows` — the row count a tool response carried, when the
150
+ * span reported one. */
151
+ export const CIVIC_TERM_RESPONSE_ROWS = 'civic:responseRows';
152
+ /**
153
+ * `civic:durationMs` — the elapsed the producer measured for one tool call.
154
+ * Emitted whenever the span carried it, on a call that answered and on one
155
+ * the source REFUSED alike: since civic-ai-tools-website#413 the reference
156
+ * producer records the elapsed on its rejection path too, so this term and
157
+ * {@link CIVIC_TERM_FAILED} appear together on the same activity.
158
+ */
159
+ export const CIVIC_TERM_DURATION_MS = 'civic:durationMs';
74
160
  /** Bind the emitters to one era's two literals. */
75
161
  export function makeCivicVocabulary(ns, urnPrefix) {
76
162
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typedstandards/civic-typed-harness",
3
- "version": "0.4.1",
3
+ "version": "0.6.0",
4
4
  "description": "Civic-domain harness for Typed Standards evidence packages — the DOMAIN layer of the ADR-0021 format/domain line: civic PROV-O vocabulary and provenance builder, datHere envelope-policy derivations, and the capture machinery (trace builder, skill-metadata extraction, data-source population, adversarial rubric core). Derives; @typedstandards/produce-core assembles.",
5
5
  "license": "MIT",
6
6
  "author": "Nathan Storey",
@@ -26,17 +26,18 @@
26
26
  "scripts": {
27
27
  "build": "tsc -p tsconfig.json",
28
28
  "clean": "rm -rf dist tsconfig.tsbuildinfo",
29
- "typecheck": "tsc -p tsconfig.json --noEmit",
29
+ "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json",
30
30
  "lint": "eslint .",
31
31
  "test": "node --test --experimental-strip-types \"src/**/*.test.ts\"",
32
32
  "prepare": "npm run build",
33
33
  "prepublishOnly": "npm run clean && npm run build"
34
34
  },
35
35
  "dependencies": {
36
- "@typedstandards/produce-core": "^0.3.0 || ^0.4.0"
36
+ "@typedstandards/produce-core": "^0.7.0"
37
37
  },
38
38
  "devDependencies": {
39
- "@typedstandards/verify-core": "^0.9.0",
39
+ "@typedstandards/verify-core": "^0.12.0",
40
+ "@types/node": "^22.20.2",
40
41
  "@typescript-eslint/parser": "^8",
41
42
  "eslint": "^9",
42
43
  "typescript": "^5.9.3"