@typedstandards/civic-typed-harness 0.2.0 → 0.3.1
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 +109 -0
- package/README.md +3 -3
- package/dist/capture/data-sources.d.ts +7 -1
- package/dist/capture/data-sources.js +21 -3
- package/dist/capture/provenance.d.ts +20 -3
- package/dist/capture/provenance.js +80 -36
- package/dist/format/vocabulary.d.ts +56 -11
- package/dist/format/vocabulary.js +59 -21
- package/dist/rubric/adversarial-eval-core.d.ts +7 -3
- package/dist/rubric/adversarial-eval-core.js +5 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,115 @@
|
|
|
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.3.1 — 2026-09-02
|
|
7
|
+
|
|
8
|
+
**The graph states what the span carried, and states absence as absence**
|
|
9
|
+
([civic-ai-tools-website#384](https://github.com/npstorey/civic-ai-tools-website/issues/384),
|
|
10
|
+
Wave N9 P-H1). Patch, not breaking: no existing export is removed or
|
|
11
|
+
retyped, and every golden byte — both vocabulary eras, all eight
|
|
12
|
+
golden-reproduction cases — is unchanged.
|
|
13
|
+
|
|
14
|
+
- **`buildProvenanceGraph` no longer invents a tool name.** An
|
|
15
|
+
`mcp_tool_call` span with no `tool.name` yields a query entity with no
|
|
16
|
+
`civic:toolName` key (omitted, not placeholdered) and a tool-call activity
|
|
17
|
+
described as `MCP tool call (<operation type>)`. Previously the builder
|
|
18
|
+
substituted `get_data`.
|
|
19
|
+
- **`buildProvenanceGraph` no longer attributes a data response to the run's
|
|
20
|
+
portal.** `civic:portalDomain` and `civic:datasetUrl` are emitted only when
|
|
21
|
+
the span carried both `tool.portal_domain` and `tool.dataset_id`. A
|
|
22
|
+
dataset-keyed span with no portal is described by its source agent's
|
|
23
|
+
registry title (`Data response from Socrata MCP Server`), the form
|
|
24
|
+
aggregate and unknown sources already took; a span with a dataset id and
|
|
25
|
+
no portal states `civic:datasetId` and mints no URL. Previously the builder
|
|
26
|
+
substituted `ProvenanceInput.portal` — the run's selected portal — which
|
|
27
|
+
attributed every `search` and `fetch` response to a portal the call never
|
|
28
|
+
addressed (the Socrata server's `search` and `fetch` take no portal and
|
|
29
|
+
answer from the portal that server is configured for). The graph does not
|
|
30
|
+
parse tool arguments: a portal embedded in a `fetch` id is not a portal the
|
|
31
|
+
span carried.
|
|
32
|
+
- **`buildDataSources` no longer mints an entry on `fallbackPortal`.** A
|
|
33
|
+
dataset-keyed call whose arguments carry `dataset_id` but no `portal`
|
|
34
|
+
contributes no `dataSources` entry — `DataSourceEntry.portalUrl` is a
|
|
35
|
+
required string in produce-core, so an entry with no portal is not a shape
|
|
36
|
+
this package can emit — and the call remains on the graph's tool-call
|
|
37
|
+
activities. Calls that carry a portal (every `get_data` call from the
|
|
38
|
+
reference producer, whose loop injects the run portal before the record is
|
|
39
|
+
built) are unchanged.
|
|
40
|
+
- **Unchanged signatures, two inputs now unused.** `ProvenanceInput.portal`
|
|
41
|
+
and the `fallbackPortal` parameter of `buildDataSources` keep their exact
|
|
42
|
+
types and positions, are accepted, and are not consulted; both say so at
|
|
43
|
+
the declaration.
|
|
44
|
+
- **Byte consequence.** A package produced by this version from a trace
|
|
45
|
+
whose tool spans all carry `tool.name`, and carry `tool.portal_domain`
|
|
46
|
+
wherever they carry a response hash, is byte-identical to one produced by
|
|
47
|
+
0.3.0. Where a span carried less, the 0.3.0 output asserted a value the
|
|
48
|
+
span did not, and this version's output differs from it by exactly that
|
|
49
|
+
assertion. Packages already signed under 0.3.0 are untouched and remain
|
|
50
|
+
verifiable exactly as published.
|
|
51
|
+
- **Dependency range widened, nothing resolved differently.**
|
|
52
|
+
`@typedstandards/produce-core` is accepted at `^0.3.0 || ^0.4.0` so a
|
|
53
|
+
consumer that takes produce-core 0.4.0 (an additive minor) resolves one
|
|
54
|
+
copy rather than nesting a second under this package. The lockfile here
|
|
55
|
+
still resolves 0.3.0.
|
|
56
|
+
|
|
57
|
+
## 0.3.0 — 2026-08-20
|
|
58
|
+
|
|
59
|
+
**Settlement-era civic vocabulary + produce-core 0.3.0**
|
|
60
|
+
([civic-ai-tools#160](https://github.com/npstorey/civic-ai-tools/issues/160) —
|
|
61
|
+
the 2026-08-19 vocabulary settlement, [ADR-0025](../../docs/adr/0025-vocabulary-settlement-evidence-excision.md);
|
|
62
|
+
canonical mapping in the specification's **Appendix J**, migration class
|
|
63
|
+
*frozen-in-signed-artifacts*). Minor, not breaking: no existing export is
|
|
64
|
+
removed or retyped, and no already-published package changes.
|
|
65
|
+
|
|
66
|
+
- **New emissions mint the settlement-era vocabulary.** `CIVIC_NS` becomes
|
|
67
|
+
`https://civicaitools.org/ns/civic/` (was
|
|
68
|
+
`https://civicaitools.org/ns/evidence/`) and `CIVIC_URN_PREFIX` becomes
|
|
69
|
+
`urn:civic-record` (was `urn:civic-evidence`). Every id emitted by
|
|
70
|
+
`buildProvenanceGraph` and every `civic:` `@context` entry moves with them.
|
|
71
|
+
**A package produced by this version is not byte-identical to one produced
|
|
72
|
+
by 0.2.0 from the same inputs** — the graph is inside the hashed envelope,
|
|
73
|
+
so its envelope hash differs. Packages already signed under 0.2.0 are
|
|
74
|
+
untouched and remain verifiable exactly as published.
|
|
75
|
+
- **The prior era stays available, exported, and reproducible.** New:
|
|
76
|
+
`PRIOR_ERA_CIVIC_NS`, `PRIOR_ERA_CIVIC_URN_PREFIX`, the `CivicVocabulary`
|
|
77
|
+
type, `makeCivicVocabulary(ns, urnPrefix)`, and the two bound eras
|
|
78
|
+
`CIVIC_VOCABULARY` / `PRIOR_ERA_CIVIC_VOCABULARY`. `ProvenanceConfig` gains
|
|
79
|
+
an optional `vocabulary` field defaulting to `CIVIC_VOCABULARY`. Supplying
|
|
80
|
+
`PRIOR_ERA_CIVIC_VOCABULARY` reproduces a pre-settlement record
|
|
81
|
+
byte-for-byte — the only sanctioned use; it is never for new emissions.
|
|
82
|
+
Unlike the other `ProvenanceConfig` fields this one is defaulted rather than
|
|
83
|
+
required: emitting the current vocabulary is not the silent-attribution
|
|
84
|
+
hazard ADR-0024 guards against.
|
|
85
|
+
- **Unchanged signatures.** `civicUrn`, `civicModelUrn`, `civicSourceAgentUrn`,
|
|
86
|
+
`civicPlatformUrn`, and `makeCivicProvContext` keep their exact signatures
|
|
87
|
+
and now delegate to `CIVIC_VOCABULARY`, so callers of the settlement era need
|
|
88
|
+
no edits.
|
|
89
|
+
- **`buildEvaluationPrompt` takes `RecordPackage`** — produce-core 0.3.0's
|
|
90
|
+
settlement-era name for the same object (Appendix J, *alias-and-deprecate*).
|
|
91
|
+
`EvidencePackage` remains a deprecated upstream alias of that exact type, so
|
|
92
|
+
existing callers still compile; the rubric test suite deliberately stays on
|
|
93
|
+
the prior name as the executable alias proof.
|
|
94
|
+
- **Dependency floor raised:** `@typedstandards/produce-core` `^0.2.0` →
|
|
95
|
+
`^0.3.0`, and the `@typedstandards/verify-core` devDependency `^0.8.0` →
|
|
96
|
+
`^0.9.0` to match the version produce-core 0.3.0 resolves (same-commit
|
|
97
|
+
manifest bump across a minor boundary). No behavior-affecting patch bumps
|
|
98
|
+
ride along.
|
|
99
|
+
- **Both eras are under test.** The two captured fixtures
|
|
100
|
+
(`reference-golden.json`, `website-golden.json`) predate the settlement and
|
|
101
|
+
are not edited: their byte- and hash-parity legs now run with the prior-era
|
|
102
|
+
vocabulary injected, and a settlement-era leg asserts the same reference
|
|
103
|
+
bytes with exactly the two Appendix J literals substituted, with the hashes
|
|
104
|
+
recomputed from those substituted bytes by verify-core's shared chain. The
|
|
105
|
+
per-canonicalization-rule consequence is pinned explicitly: under
|
|
106
|
+
`legacy-json/v1` the era flip moves `contentHash` (the whole package is
|
|
107
|
+
fingerprinted, provenance included); under `dathere-ag-jupyter/v1` it does
|
|
108
|
+
not (only the executed notebook is). The envelope hash moves under both.
|
|
109
|
+
- **Purity guard widened:** the capture/format boundary test now bars
|
|
110
|
+
vocabulary literals of **both** eras from `src/capture/**`. The invariant is
|
|
111
|
+
"vocabulary lives only in `format/vocabulary.ts`", not "the prior-era strings
|
|
112
|
+
are gone" — both eras are real vocabulary now, and either could be redefined
|
|
113
|
+
in the wrong module.
|
|
114
|
+
|
|
6
115
|
## 0.2.0 — 2026-08-17
|
|
7
116
|
|
|
8
117
|
**Breaking: identity-bearing config is now required**
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @typedstandards/civic-typed-harness
|
|
2
2
|
|
|
3
|
-
The civic **domain harness** for [Typed Standards](../../docs/architecture/typed-standards-specification.md)
|
|
3
|
+
The civic **domain harness** for [Typed Standards](../../docs/architecture/typed-standards-specification.md) record packages — the DOMAIN side of the format/domain line drawn in [ADR-0021](../../docs/adr/0021-produce-core-extraction.md), relocated from the reference app as an installable package ([ADR-0022](../../docs/adr/0022-civic-typed-harness-packaging.md)). The operating rule: **the harness derives, the core assembles** — everything here produces domain-derived values (civic vocabulary terms, datHere policy fields, provenance graphs, capture artifacts) that feed [`@typedstandards/produce-core`](https://www.npmjs.com/package/@typedstandards/produce-core)'s neutral envelope assembly.
|
|
4
4
|
|
|
5
5
|
> **Name.** The package landed in-repo under the working name `civic-typed-harness` with the final npm name deferred (ADR-0022 §B). It publishes as **`@typedstandards/civic-typed-harness`** — the standard's scope with the full layer-3 basename; the rationale, the alternatives scored, and the named reversal condition are recorded in the planning-side naming memo and summarized in the [ADR-0022 addendum](../../docs/adr/0022-civic-typed-harness-packaging.md#addendum-2026-08-02--final-npm-name).
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@ The civic **domain harness** for [Typed Standards](../../docs/architecture/typed
|
|
|
8
8
|
|
|
9
9
|
| Module group | Modules | What it is |
|
|
10
10
|
|---|---|---|
|
|
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-
|
|
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
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. |
|
|
13
13
|
| **`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
14
|
|
|
@@ -55,7 +55,7 @@ Runtime dependency: **`@typedstandards/produce-core` (`^0.2.0`) only.** The veri
|
|
|
55
55
|
|
|
56
56
|
## Byte-compatibility
|
|
57
57
|
|
|
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). 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.
|
|
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.
|
|
59
59
|
|
|
60
60
|
## Develop
|
|
61
61
|
|
|
@@ -47,12 +47,18 @@ export declare function resolveToolSource(toolCall: ToolCallSummary, span: Trace
|
|
|
47
47
|
* Build the per-source evidence-package `dataSources` array.
|
|
48
48
|
*
|
|
49
49
|
* Dataset-keyed sources (Socrata) contribute one entry per unique
|
|
50
|
-
* `dataset_id` observed across tool calls
|
|
50
|
+
* `dataset_id` observed across tool calls that also carried a `portal`
|
|
51
|
+
* argument; a dataset-keyed call that carried no portal contributes NO
|
|
52
|
+
* entry (the loop body says why). Aggregate sources (Data Commons,
|
|
51
53
|
* Boston OpenContext — registry entries carrying `aggregatePortalUrl`)
|
|
52
54
|
* contribute a single entry when any of their tool calls was made. Unknown
|
|
53
55
|
* source ids contribute no entry. Each entry is tagged with `sourceId` so
|
|
54
56
|
* downstream consumers can distinguish provenance. Emission order: the
|
|
55
57
|
* dataset-keyed entries (first-seen order), then aggregate sources in
|
|
56
58
|
* registry insertion order — matching the reference implementation.
|
|
59
|
+
*
|
|
60
|
+
* `fallbackPortal` is accepted and NOT consulted since 0.3.1: an entry
|
|
61
|
+
* states the portal the call carried, never the run's. The parameter stays
|
|
62
|
+
* in the signature so existing callers keep compiling.
|
|
57
63
|
*/
|
|
58
64
|
export declare function buildDataSources(toolCalls: ToolCallSummary[], trace: Record<string, unknown>, fallbackPortal: string, now: string, options?: DataSourceOptions): DataSourceEntry[];
|
|
@@ -51,13 +51,19 @@ export function resolveToolSource(toolCall, span, resolver = civicToolSourceReso
|
|
|
51
51
|
* Build the per-source evidence-package `dataSources` array.
|
|
52
52
|
*
|
|
53
53
|
* Dataset-keyed sources (Socrata) contribute one entry per unique
|
|
54
|
-
* `dataset_id` observed across tool calls
|
|
54
|
+
* `dataset_id` observed across tool calls that also carried a `portal`
|
|
55
|
+
* argument; a dataset-keyed call that carried no portal contributes NO
|
|
56
|
+
* entry (the loop body says why). Aggregate sources (Data Commons,
|
|
55
57
|
* Boston OpenContext — registry entries carrying `aggregatePortalUrl`)
|
|
56
58
|
* contribute a single entry when any of their tool calls was made. Unknown
|
|
57
59
|
* source ids contribute no entry. Each entry is tagged with `sourceId` so
|
|
58
60
|
* downstream consumers can distinguish provenance. Emission order: the
|
|
59
61
|
* dataset-keyed entries (first-seen order), then aggregate sources in
|
|
60
62
|
* registry insertion order — matching the reference implementation.
|
|
63
|
+
*
|
|
64
|
+
* `fallbackPortal` is accepted and NOT consulted since 0.3.1: an entry
|
|
65
|
+
* states the portal the call carried, never the run's. The parameter stays
|
|
66
|
+
* in the signature so existing callers keep compiling.
|
|
61
67
|
*/
|
|
62
68
|
export function buildDataSources(toolCalls, trace, fallbackPortal, now, options = {}) {
|
|
63
69
|
const resolver = options.resolver ?? civicToolSourceResolver;
|
|
@@ -71,8 +77,20 @@ export function buildDataSources(toolCalls, trace, fallbackPortal, now, options
|
|
|
71
77
|
const source = resolveToolSource(tc, toolSpans[i], resolver, fallbackSourceId);
|
|
72
78
|
if (isDatasetKeyedSource(source, registry)) {
|
|
73
79
|
const datasetId = tc.args.dataset_id;
|
|
74
|
-
const portal = tc.args.portal
|
|
75
|
-
|
|
80
|
+
const portal = tc.args.portal;
|
|
81
|
+
// An entry is minted only from what the call carried: a dataset id AND
|
|
82
|
+
// a portal. A dataset-keyed call with a dataset id and no portal
|
|
83
|
+
// contributes no entry. `DataSourceEntry.portalUrl` is a required
|
|
84
|
+
// string (produce-core), so "an entry with no portal" is not a shape
|
|
85
|
+
// this package can emit; and substituting the run's portal
|
|
86
|
+
// (`fallbackPortal`, before 0.3.1) attributed the call to a portal it
|
|
87
|
+
// never addressed. Omission is the honest shape — the call is still on
|
|
88
|
+
// the PROV-O graph's tool-call activities, stated without a portal.
|
|
89
|
+
// The branch is latent for the reference producer, whose loop injects
|
|
90
|
+
// the run portal into `get_data` arguments before the record is built
|
|
91
|
+
// (run-tool-loop.ts:799 at the time of writing); any caller whose
|
|
92
|
+
// summary carries `dataset_id` without `portal` reaches it.
|
|
93
|
+
if (datasetId && portal) {
|
|
76
94
|
let byDataset = datasetKeyed.get(source);
|
|
77
95
|
if (!byDataset) {
|
|
78
96
|
byDataset = new Map();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ProvGraph, type ProvNode } from '@typedstandards/produce-core';
|
|
2
|
-
import { type PlatformAgentConfig } from '../format/vocabulary.ts';
|
|
2
|
+
import { type CivicVocabulary, type PlatformAgentConfig } from '../format/vocabulary.ts';
|
|
3
3
|
import { type CivicSourceRegistry } from '../format/sources.ts';
|
|
4
4
|
export type { ProvGraph, ProvNode };
|
|
5
5
|
/** Package-level inputs to the graph build. */
|
|
@@ -15,11 +15,17 @@ export interface ProvenanceInput {
|
|
|
15
15
|
* itself is exactly this value by construction. */
|
|
16
16
|
outputHash?: string;
|
|
17
17
|
model: string;
|
|
18
|
+
/** The run's selected portal. Accepted and NOT consulted by the graph
|
|
19
|
+
* builder since 0.3.1: the graph states the portal a tool span carried
|
|
20
|
+
* (`tool.portal_domain`) and states absence as absence — it never
|
|
21
|
+
* substitutes the run's portal for one the call did not address. The
|
|
22
|
+
* field stays in the type so callers that pass it (the reference app
|
|
23
|
+
* does, as an object literal) keep compiling. */
|
|
18
24
|
portal: string;
|
|
19
25
|
}
|
|
20
26
|
/** Instance configuration for the graph build (config-not-constants). */
|
|
21
27
|
export interface ProvenanceConfig {
|
|
22
|
-
/** Platform-agent identity — the deployment publishing the
|
|
28
|
+
/** Platform-agent identity — the deployment publishing the record. */
|
|
23
29
|
platformAgent: PlatformAgentConfig;
|
|
24
30
|
/** Source registry supplying agent titles and MCP server URLs. */
|
|
25
31
|
sourceRegistry: CivicSourceRegistry;
|
|
@@ -34,9 +40,20 @@ export interface ProvenanceConfig {
|
|
|
34
40
|
* carries no `dcterms:description` at all — the field is omitted from the
|
|
35
41
|
* graph (honest omission), never filled with a fallback. */
|
|
36
42
|
modelAgentDescription?: string;
|
|
43
|
+
/** Vocabulary era to emit (spec Appendix J). Defaults to the settlement-era
|
|
44
|
+
* `CIVIC_VOCABULARY` — the only value a new emission may use. Supply
|
|
45
|
+
* `PRIOR_ERA_CIVIC_VOCABULARY` ONLY to reproduce or verify a record signed
|
|
46
|
+
* before the 2026-08-19 settlement, whose identifiers are frozen under the
|
|
47
|
+
* hash it was signed with. Unlike the other fields here this is not
|
|
48
|
+
* deployment identity, so it is defaulted rather than required: emitting
|
|
49
|
+
* the current vocabulary is never the silent-attribution hazard ADR-0024
|
|
50
|
+
* guards against. */
|
|
51
|
+
vocabulary?: CivicVocabulary;
|
|
37
52
|
}
|
|
38
53
|
/** The civicaitools.org reference deployment's values. Passed explicitly by
|
|
39
|
-
* the reference app — never applied as a default
|
|
54
|
+
* the reference app — never applied as a default, and never spread into
|
|
55
|
+
* another instance's config, which would assert infrastructure that
|
|
56
|
+
* instance doesn't run. */
|
|
40
57
|
export declare const CIVICAITOOLS_PROVENANCE_CONFIG: ProvenanceConfig;
|
|
41
58
|
/**
|
|
42
59
|
* Build a W3C PROV-O JSON-LD graph from an OTel trace and package metadata.
|
|
@@ -17,10 +17,12 @@
|
|
|
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 {
|
|
20
|
+
import { CIVIC_VOCABULARY, CIVICAITOOLS_PLATFORM_AGENT, } 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
|
-
* the reference app — never applied as a default
|
|
23
|
+
* the reference app — never applied as a default, and never spread into
|
|
24
|
+
* another instance's config, which would assert infrastructure that
|
|
25
|
+
* instance doesn't run. */
|
|
24
26
|
export const CIVICAITOOLS_PROVENANCE_CONFIG = {
|
|
25
27
|
platformAgent: CIVICAITOOLS_PLATFORM_AGENT,
|
|
26
28
|
sourceRegistry: CIVIC_SOURCE_REGISTRY,
|
|
@@ -48,15 +50,20 @@ function nanoToIso(nano) {
|
|
|
48
50
|
export function buildProvenanceGraph(trace, input, config) {
|
|
49
51
|
const otel = trace;
|
|
50
52
|
const spans = otel?.resourceSpans?.[0]?.scopeSpans?.[0]?.spans || [];
|
|
51
|
-
|
|
53
|
+
// `input.portal` is deliberately not read — see `ProvenanceInput.portal`.
|
|
54
|
+
const { packageId, promptHash, promptText, outputText, model } = input;
|
|
52
55
|
const outputHash = input.outputHash ?? hash(outputText ?? '');
|
|
53
56
|
const registry = config.sourceRegistry;
|
|
54
57
|
const fallbackSourceId = config.fallbackSourceId ?? FALLBACK_SOURCE_ID;
|
|
55
58
|
const skillSourceId = config.skillSourceId ?? FALLBACK_SOURCE_ID;
|
|
59
|
+
// Vocabulary era — settlement-era unless a prior-era reproduction asks
|
|
60
|
+
// otherwise. Every id and the `@context` below route through it, so a graph
|
|
61
|
+
// never mixes eras.
|
|
62
|
+
const vocab = config.vocabulary ?? CIVIC_VOCABULARY;
|
|
56
63
|
const graph = [];
|
|
57
64
|
// --- Entities ---
|
|
58
65
|
// User prompt
|
|
59
|
-
graph.push(makeEntityNode(
|
|
66
|
+
graph.push(makeEntityNode(vocab.urn(packageId, 'prompt', promptHash), {
|
|
60
67
|
'civic:contentHash': `sha256:${promptHash}`,
|
|
61
68
|
'dcterms:description': 'User query prompt',
|
|
62
69
|
...(promptText ? { 'prov:value': promptText } : {}),
|
|
@@ -69,13 +76,13 @@ export function buildProvenanceGraph(trace, input, config) {
|
|
|
69
76
|
const skillHash = skillSpan ? getAttr(skillSpan.attributes, 'skill.text_hash') : undefined;
|
|
70
77
|
const skillServerUrl = skillSpan ? getAttr(skillSpan.attributes, 'skill.mcp_server_url') : undefined;
|
|
71
78
|
if (skillHash) {
|
|
72
|
-
graph.push(makeEntityNode(
|
|
79
|
+
graph.push(makeEntityNode(vocab.urn(packageId, 'skill', skillHash), {
|
|
73
80
|
'civic:contentHash': `sha256:${skillHash}`,
|
|
74
81
|
'dcterms:description': 'Composed MCP skill guidance (system prompt)',
|
|
75
82
|
}, ['prov:Plan']));
|
|
76
83
|
}
|
|
77
84
|
// Final output
|
|
78
|
-
graph.push(makeEntityNode(
|
|
85
|
+
graph.push(makeEntityNode(vocab.urn(packageId, 'output', outputHash), {
|
|
79
86
|
'civic:contentHash': `sha256:${outputHash}`,
|
|
80
87
|
'dcterms:description': 'AI-generated analysis output',
|
|
81
88
|
}));
|
|
@@ -83,7 +90,7 @@ export function buildProvenanceGraph(trace, input, config) {
|
|
|
83
90
|
// LLM model. The description is emitted only when the config supplies one —
|
|
84
91
|
// absent otherwise (honest omission, same idiom as deriveProducerProfile /
|
|
85
92
|
// deriveSummaryEmission in ../format/dathere.ts).
|
|
86
|
-
const modelUrn =
|
|
93
|
+
const modelUrn = vocab.modelUrn(model);
|
|
87
94
|
graph.push(makeAgentNode(modelUrn, {
|
|
88
95
|
'dcterms:title': model,
|
|
89
96
|
...(config.modelAgentDescription
|
|
@@ -91,7 +98,7 @@ export function buildProvenanceGraph(trace, input, config) {
|
|
|
91
98
|
: {}),
|
|
92
99
|
}, ['prov:SoftwareAgent']));
|
|
93
100
|
// MCP server agents — one per distinct data source that appears in the
|
|
94
|
-
// trace. Tool spans carry `mcp.source`;
|
|
101
|
+
// trace. Tool spans carry `mcp.source`; records captured before
|
|
95
102
|
// source tagging have no source attribute, so they fall back to the
|
|
96
103
|
// configured fallback source (socrata — the only source at the time) for
|
|
97
104
|
// backwards compatibility. Agent coordinates come from the source registry;
|
|
@@ -100,7 +107,7 @@ export function buildProvenanceGraph(trace, input, config) {
|
|
|
100
107
|
const sourceAgentMap = {};
|
|
101
108
|
for (const [id, info] of Object.entries(registry)) {
|
|
102
109
|
sourceAgentMap[id] = {
|
|
103
|
-
urn:
|
|
110
|
+
urn: vocab.sourceAgentUrn(id),
|
|
104
111
|
title: info.agentTitle,
|
|
105
112
|
serverUrl: id === skillSourceId ? skillServerUrl || info.serverUrl : info.serverUrl,
|
|
106
113
|
};
|
|
@@ -112,7 +119,7 @@ export function buildProvenanceGraph(trace, input, config) {
|
|
|
112
119
|
}
|
|
113
120
|
for (const sourceId of sourcesInTrace) {
|
|
114
121
|
const meta = sourceAgentMap[sourceId] ?? {
|
|
115
|
-
urn:
|
|
122
|
+
urn: vocab.sourceAgentUrn(encodeURIComponent(sourceId)),
|
|
116
123
|
title: `${sourceId} MCP Server`,
|
|
117
124
|
serverUrl: sourceId,
|
|
118
125
|
};
|
|
@@ -129,10 +136,10 @@ export function buildProvenanceGraph(trace, input, config) {
|
|
|
129
136
|
const id = sourceId || fallbackSourceId;
|
|
130
137
|
return (sourceAgentMap[id]?.urn
|
|
131
138
|
?? sourceAgentMap[fallbackSourceId]?.urn
|
|
132
|
-
??
|
|
139
|
+
?? vocab.sourceAgentUrn(encodeURIComponent(id)));
|
|
133
140
|
}
|
|
134
141
|
// Platform
|
|
135
|
-
graph.push(makeAgentNode(
|
|
142
|
+
graph.push(makeAgentNode(vocab.platformUrn(config.platformAgent.id), {
|
|
136
143
|
'dcterms:title': config.platformAgent.title,
|
|
137
144
|
'civic:url': config.platformAgent.url,
|
|
138
145
|
}, ['prov:SoftwareAgent']));
|
|
@@ -141,11 +148,11 @@ export function buildProvenanceGraph(trace, input, config) {
|
|
|
141
148
|
const inferenceSpans = spans.filter(s => s.name === 'llm_inference');
|
|
142
149
|
const inferenceUrns = [];
|
|
143
150
|
for (const span of inferenceSpans) {
|
|
144
|
-
const spanUrn =
|
|
151
|
+
const spanUrn = vocab.urn(packageId, 'inference', span.spanId);
|
|
145
152
|
inferenceUrns.push(spanUrn);
|
|
146
|
-
const used = [provRef(
|
|
153
|
+
const used = [provRef(vocab.urn(packageId, 'prompt', promptHash))];
|
|
147
154
|
if (skillHash) {
|
|
148
|
-
used.push(provRef(
|
|
155
|
+
used.push(provRef(vocab.urn(packageId, 'skill', skillHash)));
|
|
149
156
|
}
|
|
150
157
|
const promptTokens = getAttr(span.attributes, 'gen_ai.response.prompt_tokens');
|
|
151
158
|
const completionTokens = getAttr(span.attributes, 'gen_ai.response.completion_tokens');
|
|
@@ -163,17 +170,34 @@ export function buildProvenanceGraph(trace, input, config) {
|
|
|
163
170
|
...(completionTokens ? { 'civic:completionTokens': Number(completionTokens) } : {}),
|
|
164
171
|
}));
|
|
165
172
|
}
|
|
166
|
-
// MCP tool call spans
|
|
173
|
+
// MCP tool call spans.
|
|
174
|
+
//
|
|
175
|
+
// Every node derived from a span states what the span carried, and states
|
|
176
|
+
// absence as absence: a span with no `tool.name` yields nodes that name no
|
|
177
|
+
// tool, and a span with no `tool.portal_domain` yields a data response
|
|
178
|
+
// attributed to no portal. Nothing here substitutes the run's selected
|
|
179
|
+
// portal (`input.portal`, accepted and unused since 0.3.1) or a default
|
|
180
|
+
// tool name for a value the producer did not write. The reference
|
|
181
|
+
// producer's loop always writes `tool.name`, and writes
|
|
182
|
+
// `tool.portal_domain` only when the call's arguments carried a portal —
|
|
183
|
+
// which its portal injection does for `get_data` and for no other tool.
|
|
184
|
+
// The Socrata server's `search` (one argument, `query`) and `fetch` (one
|
|
185
|
+
// argument, `id`) address the portal that server is configured for, which
|
|
186
|
+
// the producer does not know. A `fetch` id may embed a portal, but that
|
|
187
|
+
// grammar belongs to the server: the graph does not parse arguments, so an
|
|
188
|
+
// id is never a portal the span carried.
|
|
167
189
|
const dataResponseUrns = [];
|
|
168
190
|
for (const span of toolSpans) {
|
|
169
|
-
const toolCallUrn =
|
|
191
|
+
const toolCallUrn = vocab.urn(packageId, 'tool-call', span.spanId);
|
|
170
192
|
const argsStr = getAttr(span.attributes, 'tool.arguments') || '{}';
|
|
171
193
|
const queryHash = hash(argsStr);
|
|
172
194
|
const responseHash = getAttr(span.attributes, 'tool.response_hash');
|
|
173
|
-
|
|
195
|
+
// Absent when the span carried none — never defaulted.
|
|
196
|
+
const toolName = getAttr(span.attributes, 'tool.name');
|
|
174
197
|
const opType = getAttr(span.attributes, 'tool.operation_type') || 'unknown';
|
|
175
198
|
const datasetId = getAttr(span.attributes, 'tool.dataset_id');
|
|
176
|
-
|
|
199
|
+
// Absent when the span carried none — never the run's portal.
|
|
200
|
+
const portalDomain = getAttr(span.attributes, 'tool.portal_domain');
|
|
177
201
|
const toolSource = getAttr(span.attributes, 'mcp.source') || fallbackSourceId;
|
|
178
202
|
const toolAgentUrn = agentUrnForSource(toolSource);
|
|
179
203
|
const toolSourceDatasetKeyed = isDatasetKeyedSource(toolSource, registry);
|
|
@@ -183,24 +207,35 @@ export function buildProvenanceGraph(trace, input, config) {
|
|
|
183
207
|
const precedingInference = inferenceSpans
|
|
184
208
|
.filter(is => Number(is.endTimeUnixNano || '0') <= toolStart)
|
|
185
209
|
.sort((a, b) => Number(b.endTimeUnixNano || '0') - Number(a.endTimeUnixNano || '0'))[0];
|
|
186
|
-
const queryUrn =
|
|
210
|
+
const queryUrn = vocab.urn(packageId, 'query', queryHash);
|
|
187
211
|
graph.push(makeEntityNode(queryUrn, {
|
|
188
212
|
'civic:contentHash': `sha256:${queryHash}`,
|
|
189
|
-
|
|
213
|
+
// Omitted — not placeholdered — when the span named no tool.
|
|
214
|
+
...(toolName ? { 'civic:toolName': toolName } : {}),
|
|
190
215
|
'civic:operationType': opType,
|
|
191
216
|
'dcterms:description': `MCP tool arguments (${opType})`,
|
|
192
217
|
...(precedingInference
|
|
193
|
-
? provWasGeneratedBy(
|
|
218
|
+
? provWasGeneratedBy(vocab.urn(packageId, 'inference', precedingInference.spanId))
|
|
194
219
|
: {}),
|
|
195
220
|
}));
|
|
196
221
|
// Data response entity
|
|
197
222
|
if (responseHash) {
|
|
198
|
-
const dataUrn =
|
|
223
|
+
const dataUrn = vocab.urn(packageId, 'data', responseHash);
|
|
199
224
|
dataResponseUrns.push(dataUrn);
|
|
200
|
-
// Description
|
|
201
|
-
//
|
|
202
|
-
//
|
|
203
|
-
|
|
225
|
+
// Description: the portal the span carried, when it carried one;
|
|
226
|
+
// otherwise the agent that answered the call, by its registry title —
|
|
227
|
+
// the form aggregate and unknown sources already take. A dataset-keyed
|
|
228
|
+
// source whose span carried a dataset id but no portal takes the
|
|
229
|
+
// agent-title form too, with the dataset id stated below as
|
|
230
|
+
// `civic:datasetId` and no URL minted (a dataset URL needs a host the
|
|
231
|
+
// span did not carry). That branch is latent by construction for the
|
|
232
|
+
// reference producer — its loop injects the run portal into `get_data`
|
|
233
|
+
// arguments before the span opens (run-tool-loop.ts:799 at the time of
|
|
234
|
+
// writing), and `get_data` is the only tool whose arguments carry a
|
|
235
|
+
// dataset id — but any producer that writes `tool.dataset_id` without
|
|
236
|
+
// `tool.portal_domain` reaches it, so it is stated honestly rather
|
|
237
|
+
// than left dead.
|
|
238
|
+
const description = toolSourceDatasetKeyed && portalDomain
|
|
204
239
|
? `Data response from ${portalDomain}`
|
|
205
240
|
: `Data response from ${sourceAgentMap[toolSource]?.title || toolSource}`;
|
|
206
241
|
const responseRows = getAttr(span.attributes, 'tool.response_rows');
|
|
@@ -210,12 +245,18 @@ export function buildProvenanceGraph(trace, input, config) {
|
|
|
210
245
|
'civic:sourceId': toolSource,
|
|
211
246
|
...provWasGeneratedBy(toolCallUrn),
|
|
212
247
|
// Croissant 1.1 placeholder — only meaningful for dataset-keyed
|
|
213
|
-
// sources today.
|
|
248
|
+
// sources today. The dataset id is stated whenever the span carried
|
|
249
|
+
// one; the portal and the dataset URL only when the span carried
|
|
250
|
+
// the portal as well. Key order is the byte contract.
|
|
214
251
|
...(toolSourceDatasetKeyed && datasetId
|
|
215
252
|
? {
|
|
216
253
|
'civic:datasetId': datasetId,
|
|
217
|
-
|
|
218
|
-
|
|
254
|
+
...(portalDomain
|
|
255
|
+
? {
|
|
256
|
+
'civic:portalDomain': portalDomain,
|
|
257
|
+
'civic:datasetUrl': `https://${portalDomain}/d/${datasetId}`,
|
|
258
|
+
}
|
|
259
|
+
: {}),
|
|
219
260
|
'civic:croissantMetadataUrl': null, // hook for future Croissant integration
|
|
220
261
|
}
|
|
221
262
|
: {}),
|
|
@@ -227,7 +268,10 @@ export function buildProvenanceGraph(trace, input, config) {
|
|
|
227
268
|
// agent (e.g. socrata for `get_data`, data-commons for `get_observations`).
|
|
228
269
|
const durationMs = getAttr(span.attributes, 'tool.duration_ms');
|
|
229
270
|
graph.push(makeActivityNode(toolCallUrn, {
|
|
230
|
-
|
|
271
|
+
// Names the tool only when the span did.
|
|
272
|
+
'dcterms:description': toolName
|
|
273
|
+
? `MCP tool call: ${toolName} (${opType})`
|
|
274
|
+
: `MCP tool call (${opType})`,
|
|
231
275
|
'civic:sourceId': toolSource,
|
|
232
276
|
...provUsed([queryUrn]),
|
|
233
277
|
...provWasAssociatedWith(toolAgentUrn),
|
|
@@ -249,8 +293,8 @@ export function buildProvenanceGraph(trace, input, config) {
|
|
|
249
293
|
const generatorSpan = synthesisSpan || lastInference;
|
|
250
294
|
if (generatorSpan) {
|
|
251
295
|
const generatorUrn = synthesisSpan
|
|
252
|
-
?
|
|
253
|
-
:
|
|
296
|
+
? vocab.urn(packageId, 'synthesis', synthesisSpan.spanId)
|
|
297
|
+
: vocab.urn(packageId, 'inference', generatorSpan.spanId);
|
|
254
298
|
// Add synthesis activity if it exists
|
|
255
299
|
if (synthesisSpan) {
|
|
256
300
|
graph.push(makeActivityNode(generatorUrn, {
|
|
@@ -265,7 +309,7 @@ export function buildProvenanceGraph(trace, input, config) {
|
|
|
265
309
|
}));
|
|
266
310
|
}
|
|
267
311
|
// Output wasGeneratedBy
|
|
268
|
-
graph.push(makeEntityNode(
|
|
312
|
+
graph.push(makeEntityNode(vocab.urn(packageId, 'output', outputHash), {
|
|
269
313
|
...provWasGeneratedBy(generatorUrn),
|
|
270
314
|
...(dataResponseUrns.length > 0
|
|
271
315
|
? provWasDerivedFrom(dataResponseUrns)
|
|
@@ -288,9 +332,9 @@ export function buildProvenanceGraph(trace, input, config) {
|
|
|
288
332
|
'prov:qualifiedAssociation': {
|
|
289
333
|
'@type': 'prov:Association',
|
|
290
334
|
'prov:agent': provRef(modelUrn),
|
|
291
|
-
'prov:hadPlan': provRef(
|
|
335
|
+
'prov:hadPlan': provRef(vocab.urn(packageId, 'skill', skillHash)),
|
|
292
336
|
},
|
|
293
337
|
}));
|
|
294
338
|
}
|
|
295
|
-
return makeProvGraph(
|
|
339
|
+
return makeProvGraph(vocab.context(), graph);
|
|
296
340
|
}
|
|
@@ -1,24 +1,68 @@
|
|
|
1
1
|
/** The `civic:` JSON-LD namespace URI. Part of the civic vocabulary itself
|
|
2
2
|
* (it names the term set, not a deployment), so it is a constant, not a
|
|
3
|
-
* config input. */
|
|
4
|
-
export declare const CIVIC_NS = "https://civicaitools.org/ns/
|
|
5
|
-
/**
|
|
3
|
+
* config input. Settlement-era value (Appendix J). */
|
|
4
|
+
export declare const CIVIC_NS = "https://civicaitools.org/ns/civic/";
|
|
5
|
+
/** Root of the `urn:civic-record:` id scheme. Settlement-era value
|
|
6
|
+
* (Appendix J). */
|
|
7
|
+
export declare const CIVIC_URN_PREFIX = "urn:civic-record";
|
|
8
|
+
/** The prior-era `civic:` namespace URI, frozen inside records signed before
|
|
9
|
+
* the 2026-08-19 settlement. Never emitted for new packages. */
|
|
10
|
+
export declare const PRIOR_ERA_CIVIC_NS = "https://civicaitools.org/ns/evidence/";
|
|
11
|
+
/** The prior-era id-scheme root, frozen inside records signed before the
|
|
12
|
+
* 2026-08-19 settlement. Never emitted for new packages. */
|
|
13
|
+
export declare const PRIOR_ERA_CIVIC_URN_PREFIX = "urn:civic-evidence";
|
|
14
|
+
/**
|
|
15
|
+
* One era of the civic vocabulary: the two literals plus the emitters bound
|
|
16
|
+
* to them. The vocabulary is not deployment configuration (a deployment does
|
|
17
|
+
* not choose its own term set); this seam exists only so a prior-era package
|
|
18
|
+
* can be reproduced with the exact vocabulary it was signed under.
|
|
19
|
+
*/
|
|
20
|
+
export interface CivicVocabulary {
|
|
21
|
+
/** The `civic:` JSON-LD namespace URI for this era. */
|
|
22
|
+
readonly ns: string;
|
|
23
|
+
/** The id-scheme root for this era. */
|
|
24
|
+
readonly urnPrefix: string;
|
|
25
|
+
/** The civic record-package JSON-LD `@context`: prov / xsd / civic /
|
|
26
|
+
* dcterms, in the reference emission order (legacy-chain byte discipline —
|
|
27
|
+
* insertion order is the byte contract). */
|
|
28
|
+
context(): Record<string, string>;
|
|
29
|
+
/** Package-scoped node id: `<prefix>:<packageId>:<type>:<id>`. */
|
|
30
|
+
urn(packageId: string, type: string, id: string): string;
|
|
31
|
+
/** Model-agent id: `<prefix>:model:<modelId>` (slashes in the model
|
|
32
|
+
* identifier collapse to dashes). */
|
|
33
|
+
modelUrn(model: string): string;
|
|
34
|
+
/** MCP source-agent id: `<prefix>:mcp-server:<sourceId>`. Known source ids
|
|
35
|
+
* are emitted raw (they are already URN-safe by construction); callers
|
|
36
|
+
* emitting an UNKNOWN id must encode it first (the capture-side builder
|
|
37
|
+
* uses `encodeURIComponent`, matching the reference behavior). */
|
|
38
|
+
sourceAgentUrn(sourceId: string): string;
|
|
39
|
+
/** Platform-agent id: `<prefix>:platform:<platformId>`. */
|
|
40
|
+
platformUrn(platformId: string): string;
|
|
41
|
+
}
|
|
42
|
+
/** Bind the emitters to one era's two literals. */
|
|
43
|
+
export declare function makeCivicVocabulary(ns: string, urnPrefix: string): CivicVocabulary;
|
|
44
|
+
/** The settlement-era vocabulary — what every new emission uses. */
|
|
45
|
+
export declare const CIVIC_VOCABULARY: CivicVocabulary;
|
|
46
|
+
/** The prior-era vocabulary, valid forever inside records signed before the
|
|
47
|
+
* settlement. Supply it to reproduce or verify such a record byte-for-byte;
|
|
48
|
+
* never to mint a new package. */
|
|
49
|
+
export declare const PRIOR_ERA_CIVIC_VOCABULARY: CivicVocabulary;
|
|
50
|
+
/** The civic record-package JSON-LD `@context`: prov / xsd / civic /
|
|
6
51
|
* dcterms, in the reference emission order (legacy-chain byte discipline —
|
|
7
|
-
* insertion order is the byte contract).
|
|
52
|
+
* insertion order is the byte contract). Settlement era; for a prior-era
|
|
53
|
+
* graph use `PRIOR_ERA_CIVIC_VOCABULARY.context()`. */
|
|
8
54
|
export declare function makeCivicProvContext(): Record<string, string>;
|
|
9
|
-
/**
|
|
10
|
-
export declare const CIVIC_URN_PREFIX = "urn:civic-evidence";
|
|
11
|
-
/** Package-scoped node id: `urn:civic-evidence:<packageId>:<type>:<id>`. */
|
|
55
|
+
/** Package-scoped node id: `urn:civic-record:<packageId>:<type>:<id>`. */
|
|
12
56
|
export declare function civicUrn(packageId: string, type: string, id: string): string;
|
|
13
|
-
/** Model-agent id: `urn:civic-
|
|
57
|
+
/** Model-agent id: `urn:civic-record:model:<modelId>` (slashes in the
|
|
14
58
|
* model identifier collapse to dashes). */
|
|
15
59
|
export declare function civicModelUrn(model: string): string;
|
|
16
|
-
/** MCP source-agent id: `urn:civic-
|
|
60
|
+
/** MCP source-agent id: `urn:civic-record:mcp-server:<sourceId>`. Known
|
|
17
61
|
* source ids are emitted raw (they are already URN-safe by construction);
|
|
18
62
|
* callers emitting an UNKNOWN id must encode it first (the capture-side
|
|
19
63
|
* builder uses `encodeURIComponent`, matching the reference behavior). */
|
|
20
64
|
export declare function civicSourceAgentUrn(sourceId: string): string;
|
|
21
|
-
/** Platform-agent id: `urn:civic-
|
|
65
|
+
/** Platform-agent id: `urn:civic-record:platform:<platformId>`. */
|
|
22
66
|
export declare function civicPlatformUrn(platformId: string): string;
|
|
23
67
|
/**
|
|
24
68
|
* Platform-agent identity — WHO published, as a PROV agent. A typed config
|
|
@@ -26,7 +70,8 @@ export declare function civicPlatformUrn(platformId: string): string;
|
|
|
26
70
|
* the civicaitools.org demo values are the exported default below.
|
|
27
71
|
*/
|
|
28
72
|
export interface PlatformAgentConfig {
|
|
29
|
-
/** Stable platform id — becomes `urn:civic-
|
|
73
|
+
/** Stable platform id — becomes `urn:civic-record:platform:<id>` (prior-era
|
|
74
|
+
* graphs carry the same id under the prior-era prefix). */
|
|
30
75
|
id: string;
|
|
31
76
|
/** Human-readable platform title (`dcterms:title`). */
|
|
32
77
|
title: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Civic PROV-O vocabulary (FORMAT-EXTENSION group) — the `civic:` JSON-LD
|
|
2
|
-
// namespace, the `urn:civic-
|
|
2
|
+
// namespace, the `urn:civic-record:` id scheme, and the platform-agent
|
|
3
3
|
// identity shape. Relocated from civic-ai-tools-website
|
|
4
4
|
// `src/lib/evidence/provenance.ts` (`:51` urn scheme, `:179–184` platform
|
|
5
5
|
// agent, `:384–391` context) per the S2 brief §1.
|
|
@@ -7,43 +7,81 @@
|
|
|
7
7
|
// This module defines vocabulary; it never walks a trace. The capture-side
|
|
8
8
|
// provenance BUILDER (src/capture/provenance.ts) imports its terms from here —
|
|
9
9
|
// that direction is the package's internal module boundary.
|
|
10
|
+
//
|
|
11
|
+
// TWO ERAS (spec Appendix J — the 2026-08-19 vocabulary settlement, migration
|
|
12
|
+
// class "frozen-in-signed-artifacts"). New emissions mint the SETTLEMENT-era
|
|
13
|
+
// terms (`CIVIC_NS` / `CIVIC_URN_PREFIX` below, the module's canonical
|
|
14
|
+
// values). The PRIOR-era terms are frozen inside every already-signed record —
|
|
15
|
+
// rewriting them would change the envelope hash and invalidate the signature —
|
|
16
|
+
// so they remain valid forever and are exported here too: reproducing or
|
|
17
|
+
// verifying a prior-era package byte-for-byte requires emitting its vocabulary
|
|
18
|
+
// exactly as it was signed. Era is not a trust signal (Appendix J §J.4 rule 2);
|
|
19
|
+
// prior-era injection exists for reproduction and verification, never for new
|
|
20
|
+
// emissions.
|
|
10
21
|
import { makeProvContext, PROV_NS, XSD_NS, DCTERMS_NS } from '@typedstandards/produce-core';
|
|
11
22
|
/** The `civic:` JSON-LD namespace URI. Part of the civic vocabulary itself
|
|
12
23
|
* (it names the term set, not a deployment), so it is a constant, not a
|
|
13
|
-
* config input. */
|
|
14
|
-
export const CIVIC_NS = 'https://civicaitools.org/ns/
|
|
15
|
-
/**
|
|
24
|
+
* config input. Settlement-era value (Appendix J). */
|
|
25
|
+
export const CIVIC_NS = 'https://civicaitools.org/ns/civic/';
|
|
26
|
+
/** Root of the `urn:civic-record:` id scheme. Settlement-era value
|
|
27
|
+
* (Appendix J). */
|
|
28
|
+
export const CIVIC_URN_PREFIX = 'urn:civic-record';
|
|
29
|
+
/** The prior-era `civic:` namespace URI, frozen inside records signed before
|
|
30
|
+
* the 2026-08-19 settlement. Never emitted for new packages. */
|
|
31
|
+
export const PRIOR_ERA_CIVIC_NS = 'https://civicaitools.org/ns/evidence/';
|
|
32
|
+
/** The prior-era id-scheme root, frozen inside records signed before the
|
|
33
|
+
* 2026-08-19 settlement. Never emitted for new packages. */
|
|
34
|
+
export const PRIOR_ERA_CIVIC_URN_PREFIX = 'urn:civic-evidence';
|
|
35
|
+
/** Bind the emitters to one era's two literals. */
|
|
36
|
+
export function makeCivicVocabulary(ns, urnPrefix) {
|
|
37
|
+
return {
|
|
38
|
+
ns,
|
|
39
|
+
urnPrefix,
|
|
40
|
+
context: () => makeProvContext({
|
|
41
|
+
prov: PROV_NS,
|
|
42
|
+
xsd: XSD_NS,
|
|
43
|
+
civic: ns,
|
|
44
|
+
dcterms: DCTERMS_NS,
|
|
45
|
+
}),
|
|
46
|
+
urn: (packageId, type, id) => `${urnPrefix}:${packageId}:${type}:${id}`,
|
|
47
|
+
modelUrn: (model) => `${urnPrefix}:model:${model.replace(/\//g, '-')}`,
|
|
48
|
+
sourceAgentUrn: (sourceId) => `${urnPrefix}:mcp-server:${sourceId}`,
|
|
49
|
+
platformUrn: (platformId) => `${urnPrefix}:platform:${platformId}`,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/** The settlement-era vocabulary — what every new emission uses. */
|
|
53
|
+
export const CIVIC_VOCABULARY = makeCivicVocabulary(CIVIC_NS, CIVIC_URN_PREFIX);
|
|
54
|
+
/** The prior-era vocabulary, valid forever inside records signed before the
|
|
55
|
+
* settlement. Supply it to reproduce or verify such a record byte-for-byte;
|
|
56
|
+
* never to mint a new package. */
|
|
57
|
+
export const PRIOR_ERA_CIVIC_VOCABULARY = makeCivicVocabulary(PRIOR_ERA_CIVIC_NS, PRIOR_ERA_CIVIC_URN_PREFIX);
|
|
58
|
+
// --- Settlement-era convenience bindings (unchanged signatures) ---
|
|
59
|
+
/** The civic record-package JSON-LD `@context`: prov / xsd / civic /
|
|
16
60
|
* dcterms, in the reference emission order (legacy-chain byte discipline —
|
|
17
|
-
* insertion order is the byte contract).
|
|
61
|
+
* insertion order is the byte contract). Settlement era; for a prior-era
|
|
62
|
+
* graph use `PRIOR_ERA_CIVIC_VOCABULARY.context()`. */
|
|
18
63
|
export function makeCivicProvContext() {
|
|
19
|
-
return
|
|
20
|
-
prov: PROV_NS,
|
|
21
|
-
xsd: XSD_NS,
|
|
22
|
-
civic: CIVIC_NS,
|
|
23
|
-
dcterms: DCTERMS_NS,
|
|
24
|
-
});
|
|
64
|
+
return CIVIC_VOCABULARY.context();
|
|
25
65
|
}
|
|
26
|
-
/**
|
|
27
|
-
export const CIVIC_URN_PREFIX = 'urn:civic-evidence';
|
|
28
|
-
/** Package-scoped node id: `urn:civic-evidence:<packageId>:<type>:<id>`. */
|
|
66
|
+
/** Package-scoped node id: `urn:civic-record:<packageId>:<type>:<id>`. */
|
|
29
67
|
export function civicUrn(packageId, type, id) {
|
|
30
|
-
return
|
|
68
|
+
return CIVIC_VOCABULARY.urn(packageId, type, id);
|
|
31
69
|
}
|
|
32
|
-
/** Model-agent id: `urn:civic-
|
|
70
|
+
/** Model-agent id: `urn:civic-record:model:<modelId>` (slashes in the
|
|
33
71
|
* model identifier collapse to dashes). */
|
|
34
72
|
export function civicModelUrn(model) {
|
|
35
|
-
return
|
|
73
|
+
return CIVIC_VOCABULARY.modelUrn(model);
|
|
36
74
|
}
|
|
37
|
-
/** MCP source-agent id: `urn:civic-
|
|
75
|
+
/** MCP source-agent id: `urn:civic-record:mcp-server:<sourceId>`. Known
|
|
38
76
|
* source ids are emitted raw (they are already URN-safe by construction);
|
|
39
77
|
* callers emitting an UNKNOWN id must encode it first (the capture-side
|
|
40
78
|
* builder uses `encodeURIComponent`, matching the reference behavior). */
|
|
41
79
|
export function civicSourceAgentUrn(sourceId) {
|
|
42
|
-
return
|
|
80
|
+
return CIVIC_VOCABULARY.sourceAgentUrn(sourceId);
|
|
43
81
|
}
|
|
44
|
-
/** Platform-agent id: `urn:civic-
|
|
82
|
+
/** Platform-agent id: `urn:civic-record:platform:<platformId>`. */
|
|
45
83
|
export function civicPlatformUrn(platformId) {
|
|
46
|
-
return
|
|
84
|
+
return CIVIC_VOCABULARY.platformUrn(platformId);
|
|
47
85
|
}
|
|
48
86
|
/** Demo default: the civicaitools.org reference deployment's identity. */
|
|
49
87
|
export const CIVICAITOOLS_PLATFORM_AGENT = {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type RecordPackage, type EvaluationResults } from '@typedstandards/produce-core';
|
|
2
2
|
/** Rubric identifier carried as the `scoringRubric` payload field and the
|
|
3
3
|
* methodology `testSet`. Six criteria, 1-10 each, plus aggregate. */
|
|
4
4
|
export declare const RUBRIC_ID = "civicaitools-adversarial-rubric/six-criterion-v1";
|
|
@@ -8,8 +8,12 @@ export declare const EVALUATION_RUBRIC = "You are an independent evaluator asses
|
|
|
8
8
|
* once at module load. Any wording change to the rubric produces a new
|
|
9
9
|
* version, so an attestation pins the exact prompt set that scored it. */
|
|
10
10
|
export declare const RUBRIC_VERSION_SHA256: string;
|
|
11
|
-
/** Build the user-turn evaluation content from a package's signed fields.
|
|
12
|
-
|
|
11
|
+
/** Build the user-turn evaluation content from a package's signed fields.
|
|
12
|
+
* Takes `RecordPackage` — produce-core 0.3.0's settlement-era name for the
|
|
13
|
+
* same object (spec Appendix J, migration class alias-and-deprecate). The
|
|
14
|
+
* prior name is a deprecated alias of this exact type upstream, so existing
|
|
15
|
+
* callers still compile unchanged. */
|
|
16
|
+
export declare function buildEvaluationPrompt(pkg: RecordPackage): string;
|
|
13
17
|
export type ParsedEvaluation = {
|
|
14
18
|
ok: true;
|
|
15
19
|
results: EvaluationResults;
|
|
@@ -59,7 +59,11 @@ Respond in this exact JSON format (no markdown fences, just raw JSON):
|
|
|
59
59
|
* once at module load. Any wording change to the rubric produces a new
|
|
60
60
|
* version, so an attestation pins the exact prompt set that scored it. */
|
|
61
61
|
export const RUBRIC_VERSION_SHA256 = sha256Hex(EVALUATION_RUBRIC);
|
|
62
|
-
/** Build the user-turn evaluation content from a package's signed fields.
|
|
62
|
+
/** Build the user-turn evaluation content from a package's signed fields.
|
|
63
|
+
* Takes `RecordPackage` — produce-core 0.3.0's settlement-era name for the
|
|
64
|
+
* same object (spec Appendix J, migration class alias-and-deprecate). The
|
|
65
|
+
* prior name is a deprecated alias of this exact type upstream, so existing
|
|
66
|
+
* callers still compile unchanged. */
|
|
63
67
|
export function buildEvaluationPrompt(pkg) {
|
|
64
68
|
const toolCallSummary = pkg.queries
|
|
65
69
|
.map((q, i) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@typedstandards/civic-typed-harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
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",
|
|
@@ -33,10 +33,10 @@
|
|
|
33
33
|
"prepublishOnly": "npm run clean && npm run build"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@typedstandards/produce-core": "^0.
|
|
36
|
+
"@typedstandards/produce-core": "^0.3.0 || ^0.4.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@typedstandards/verify-core": "^0.
|
|
39
|
+
"@typedstandards/verify-core": "^0.9.0",
|
|
40
40
|
"@typescript-eslint/parser": "^8",
|
|
41
41
|
"eslint": "^9",
|
|
42
42
|
"typescript": "^5.9.3"
|