You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Data Schematic: Replace Provenance free-text info with TypedStandards cryptographic signature #4448
Investigation: what replacing free-text provenance with TypedStandards actually entails
(Investigated by Fable 5 against npstorey/typedstandardsmain as of 2026-08-26 and the Typed Standards Specification v0.1 Working Draft in npstorey/civic-ai-tools.)
Where the free-text provenance lives today
There are three provenance surfaces in a Data Schematic, not one:
Info-drawer metadata table (src/cmd/viz.rs): Generated by: qsv <version> + Compiled: timestamp + the optional --dataset-pid link. This is what satisfies MUST 9 (Provenance) of docs/DATA_SCHEMATIC.md — and it is pure, unverifiable text.
Data Dictionary drawer footer: the x-qsv.generated_by attribution block authored by describegpt (command line, prompt file, model, LLM API URL, language, timestamp), stored verbatim in the JSON Schema and localized at render time.
The documented Level 3 gap (docs/DATA_SCHEMATIC.md, "Level 3 is not reached today"): --dict-info bundles the schema, the charted frequency counts and the consumed stats sidecars into the HTML, but the bundled inputs carry no integrity digests — exactly the attestation MUST 8 requires.
What TypedStandards offers (current state)
The format: a record package is a single canonical JSON object. Chain (spec §8.2/§8.3.1): unsigned envelope → RFC 8785 JCS canonicalization → SHA-256 envelope hash → Ed25519ph signature computed over the UTF-8 bytes of the hex string of that hash (a deliberate spec quirk that any independent implementation must match exactly). The signature envelope is {signature, publicKey (base64 DER SPKI), algorithm: "Ed25519ph", kid}, with kid resolvable in a publisher's trust registry (§8.3.3).
Timestamps / transparency: RFC 3161 (freetsa.org) + Sigstore Rekor inclusion proofs are SHOULD / best-effort (§8.3.2). Important for viz: these are the only network legs, and they're optional — offline generation survives.
Content hashing: contentHash is a multihash digest set — sha256 required, blake3 a registered alternate (qsv already ships blake3).
Reference implementations are TypeScript/npm: @typedstandards/verify-core and @typedstandards/produce-core. produce-core is deliberately I/O-free with caller-supplied keys and byte-golden fixtures, explicitly so that "an independent implementation can emit packages that verify under spec §9.2" — i.e. a Rust producer inside qsv is an intended use case, and the byte-golden fixture suite doubles as cross-language test vectors.
Verification UX: typedstandards.org /verify resolves bare identifiers against bareIdentifierHost; cross-host links are /verify/<host>/<id>. Crucially, the §8.8 commitment view with ?inline=1 is a self-contained bundle that verifies with zero network access (§9.4) — the relevant pattern for a locally-generated CLI artifact that has no hosting origin.
The catch: content-profile mismatch
v0.1 defines exactly one content profile — literally named datHere (§8.7) — but it is the A–G envelope for AI-assisted chat analysis. Its normative requirements (§8.7.1) include prompt.text with full visibility, a non-empty system prompt (skillMetadata.skillText), a deliberative trace, a Jupyter notebook (section E), and a rendered answer. A Data Schematic satisfies essentially none of that — MUST 1 of docs/DATA_SCHEMATIC.md says panels are cache-derived, not model-authored.
There are really two distinct artifacts here, with different fits:
Artifact
Producer
TypedStandards fit
The data dictionary
describegpt (LLM-produced)
Maps naturally onto the AI-assisted-analysis story: prompt / model / output = the JSON Schema. Closest to the existing datHere profile, though still not a chat trace + notebook.
The schematic itself
deterministic derivation from the stats & frequency caches
Needs a content profile that doesn't exist yet. Even the reserved Producer Profile types (Human, Hybrid, Sandbox-only) don't cover "deterministic tool output".
The spec says new profiles land "naming the motivating adopter per the Xanadu doctrine" — qsv would be that motivating adopter. So the first step is upstream coordination (cc @npstorey), not qsv code: spec a data-schematic (or similar) content profile plus a canonicalization rule for what the content hash fingerprints (the natural candidate: the dataset + every --dict-info bundled input).
Feasibility in Rust
All primitives exist as maintained crates: serde_json_canonicalizer (RFC 8785 JCS), ed25519-dalek (sign_prehashed = Ed25519ph per RFC 8032 §5.1), sha2, and blake3 already in-tree. The format layer is small. The real design work is:
Key custody: who signs a locally-generated dashboard? Likely --sign-key <file> / env var, with the publisher (a portal, an org) hosting the trust registry at their origin. Not signing must stay first-class (produce-core treats it that way too): an unsigned envelope + envelope hash already delivers content-addressed integrity without identity.
Identifier / verify story: a CLI artifact has no origin, so the inline commitment-view bundle (§9.4 zero-network verification) is the natural default, with hosted /verify/<host>/<id> links for publishers who serve their packages.
Suggested phasing
Phase 0 (upstream, blocking) — with @npstorey: spec a Data Schematic content profile + canonicalization-rule URI; settle the CLI-artifact identifier/verify story; decide whether the describegpt dictionary gets its own (AI-assisted) package or rides inside the schematic package.
Phase 1 (qsv, no keys required) — compute multihash digests (sha256 + blake3) for the dataset and every --dict-info bundled sidecar; emit an unsigned JCS envelope (embedded and/or .package.json sidecar); surface the envelope hash in the meta table alongside — eventually instead of — the free-text row. This alone closes the documented Level 3 gap in DATA_SCHEMATIC.md and is valuable even if signing never lands.
Phase 2 (signing) — Ed25519ph via --sign-key; signature envelope embedded in the HTML + sidecar; /verify link or inline commitment bundle; vendor produce-core's byte-golden fixtures as interop tests so the Rust producer provably emits packages verify-core accepts.
Phase 3 (optional, network) — RFC 3161 timestamp + Rekor inclusion behind a flag, preserving offline generation by default.
One framing note
"Replace" may be too strong for the human-readable rows: the meta table's Generated by: / Compiled: lines are the rendered provenance a reader sees, and TypedStandards itself keeps human-readable fields alongside the signed envelope. The likely end state is the free text backed by (and re-derived from) the signed package, rather than deleted.
Content profile. No new contentProfile value (ADR-0029 §5): the profile is named by producerProfile: "scripted-recomputation/<subtype>", with a subtype you name, and metadata.contentProfile is omitted or "default". metadata.captureMethod: "tool-emitted": the program that computed the content writes the package itself, hashing the bytes as it writes them.
Canonicalization-rule URI.contentCanonicalization: "https://typedstandards.org/canonicalization/raw-bytes/v1". contentHash.sha256 is the SHA-256 of the output file's exact bytes, the digest sha256sum prints.
CLI-artifact identifier/verify story.signer.identifier is a did:key (Ed25519) derived from the signing key, with bindingTier: "pseudonymous". No domain and no trust registry needed. A verifier recomputes the identifier from the key and reports trust status self_certified: one key signed everything under that identifier, which says nothing about who holds the key. A package signed this way verifies with no network access.
Whether the describegpt dictionary gets its own package stays your call.
For interop tests:
produce-core 0.5.0's byte-golden suite includes v01-self-certified-signer.
The public verifier at typedstandards.org shows a did:key signer's package as self-certified, never as a known publisher.
Limits:
No real package is signed with did:key yet.
No package carries tool-emitted yet; the q15 fixture carries script-run, and verify-core 0.10.0 accepts both. ADR-0029 rests tool-emitted on your published Phase 1.
raw-bytes/v1 covers one output file. A rule over a set of files (the dataset + every --dict-info input) is not settled.
sha256 is required. A producer MAY list blake3 alongside it, but v0.1 verifiers check sha256 only.
did:key has no rotation and no revocation. A compromised key can only be abandoned.
Activity
jqnatividad commentedon Aug 27, 2026
Investigation: what replacing free-text provenance with TypedStandards actually entails
(Investigated by Fable 5 against
npstorey/typedstandardsmainas of 2026-08-26 and the Typed Standards Specification v0.1 Working Draft innpstorey/civic-ai-tools.)Where the free-text provenance lives today
There are three provenance surfaces in a Data Schematic, not one:
src/cmd/viz.rs):Generated by: qsv <version>+Compiled:timestamp + the optional--dataset-pidlink. This is what satisfies MUST 9 (Provenance) ofdocs/DATA_SCHEMATIC.md— and it is pure, unverifiable text.x-qsv.generated_byattribution block authored bydescribegpt(command line, prompt file, model, LLM API URL, language, timestamp), stored verbatim in the JSON Schema and localized at render time.docs/DATA_SCHEMATIC.md, "Level 3 is not reached today"):--dict-infobundles the schema, the charted frequency counts and the consumed stats sidecars into the HTML, but the bundled inputs carry no integrity digests — exactly the attestation MUST 8 requires.What TypedStandards offers (current state)
{signature, publicKey (base64 DER SPKI), algorithm: "Ed25519ph", kid}, withkidresolvable in a publisher's trust registry (§8.3.3).viz: these are the only network legs, and they're optional — offline generation survives.contentHashis a multihash digest set —sha256required,blake3a registered alternate (qsv already ships blake3).@typedstandards/verify-coreand@typedstandards/produce-core. produce-core is deliberately I/O-free with caller-supplied keys and byte-golden fixtures, explicitly so that "an independent implementation can emit packages that verify under spec §9.2" — i.e. a Rust producer inside qsv is an intended use case, and the byte-golden fixture suite doubles as cross-language test vectors./verifyresolves bare identifiers againstbareIdentifierHost; cross-host links are/verify/<host>/<id>. Crucially, the §8.8 commitment view with?inline=1is a self-contained bundle that verifies with zero network access (§9.4) — the relevant pattern for a locally-generated CLI artifact that has no hosting origin.The catch: content-profile mismatch
v0.1 defines exactly one content profile — literally named
datHere(§8.7) — but it is the A–G envelope for AI-assisted chat analysis. Its normative requirements (§8.7.1) includeprompt.textwith full visibility, a non-empty system prompt (skillMetadata.skillText), a deliberative trace, a Jupyter notebook (section E), and a rendered answer. A Data Schematic satisfies essentially none of that — MUST 1 ofdocs/DATA_SCHEMATIC.mdsays panels are cache-derived, not model-authored.There are really two distinct artifacts here, with different fits:
describegpt(LLM-produced)datHereprofile, though still not a chat trace + notebook.The spec says new profiles land "naming the motivating adopter per the Xanadu doctrine" — qsv would be that motivating adopter. So the first step is upstream coordination (cc @npstorey), not qsv code: spec a
data-schematic(or similar) content profile plus a canonicalization rule for what the content hash fingerprints (the natural candidate: the dataset + every--dict-infobundled input).Feasibility in Rust
All primitives exist as maintained crates:
serde_json_canonicalizer(RFC 8785 JCS),ed25519-dalek(sign_prehashed= Ed25519ph per RFC 8032 §5.1),sha2, and blake3 already in-tree. The format layer is small. The real design work is:--sign-key <file>/ env var, with the publisher (a portal, an org) hosting the trust registry at their origin. Not signing must stay first-class (produce-core treats it that way too): an unsigned envelope + envelope hash already delivers content-addressed integrity without identity./verify/<host>/<id>links for publishers who serve their packages.Suggested phasing
sha256+blake3) for the dataset and every--dict-infobundled sidecar; emit an unsigned JCS envelope (embedded and/or.package.jsonsidecar); surface the envelope hash in the meta table alongside — eventually instead of — the free-text row. This alone closes the documented Level 3 gap in DATA_SCHEMATIC.md and is valuable even if signing never lands.--sign-key; signature envelope embedded in the HTML + sidecar;/verifylink or inline commitment bundle; vendor produce-core's byte-golden fixtures as interop tests so the Rust producer provably emits packagesverify-coreaccepts.One framing note
"Replace" may be too strong for the human-readable rows: the meta table's
Generated by:/Compiled:lines are the rendered provenance a reader sees, and TypedStandards itself keeps human-readable fields alongside the signed envelope. The likely end state is the free text backed by (and re-derived from) the signed package, rather than deleted.jqnatividad commentedon Sep 13, 2026
Revisit once TypedStandards supports something like Decentralized Identifiers
https://www.w3.org/TR/did/
npstorey commentedon Sep 22, 2026
Released:
v0.1.9-typed-standards-spec)did:key)@typedstandards/verify-core0.10.0 and@typedstandards/produce-core0.5.0Against your Phase 0:
contentProfilevalue (ADR-0029 §5): the profile is named byproducerProfile: "scripted-recomputation/<subtype>", with a subtype you name, andmetadata.contentProfileis omitted or"default".metadata.captureMethod: "tool-emitted": the program that computed the content writes the package itself, hashing the bytes as it writes them.contentCanonicalization: "https://typedstandards.org/canonicalization/raw-bytes/v1".contentHash.sha256is the SHA-256 of the output file's exact bytes, the digestsha256sumprints.signer.identifieris adid:key(Ed25519) derived from the signing key, withbindingTier: "pseudonymous". No domain and no trust registry needed. A verifier recomputes the identifier from the key and reports trust statusself_certified: one key signed everything under that identifier, which says nothing about who holds the key. A package signed this way verifies with no network access.describegptdictionary gets its own package stays your call.For interop tests:
v01-self-certified-signer.raw-bytes/v1fixture for independent implementations:q15-self-certified.json, its output fileq15-self-certified.output.csv, and the program that minted it,q15-self-certified.mint.ts.The public verifier at typedstandards.org shows a
did:keysigner's package as self-certified, never as a known publisher.Limits:
did:keyyet.tool-emittedyet; the q15 fixture carriesscript-run, and verify-core 0.10.0 accepts both. ADR-0029 reststool-emittedon your published Phase 1.raw-bytes/v1covers one output file. A rule over a set of files (the dataset + every--dict-infoinput) is not settled.sha256is required. A producer MAY listblake3alongside it, but v0.1 verifiers checksha256only.did:keyhas no rotation and no revocation. A compromised key can only be abandoned.