Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,41 @@ The last command creates a self-contained, script-free review report. Try the co

The complete receipt also contains its format version, creation time, content-addressed source metadata, schema digest, structured issues, and a digest over the receipt payload.

## Receipt format

Receipt format version 1 is described by the published
[JSON Schema](packages/grounded-json/schemas/v1/receipt.schema.json)
(draft 2020-12). The same file ships in the npm package as
`grounded-json/receipt.schema.json`; its `$id` and `manifest.version: 1`
identify the supported receipt format independently of the npm package version.

External tools can resolve the schema locally without fetching it:

```js
import { readFileSync } from 'node:fs';

const receiptSchema = JSON.parse(
readFileSync(
new URL(import.meta.resolve('grounded-json/receipt.schema.json')),
'utf8',
),
);
```

Use a draft 2020-12 validator to check receipt structure. This is distinct from
`receipt.schema.document`, which describes the extracted **data**. The receipt
schema checks required fields, supported versions, enums, and structural bounds;
it does not replace `parseReceipt` or `verifyReceipt`. Timestamp parsing, UTF-8
byte limits, cross-field consistency, safe extraction-schema validation, hashes,
and evidence replay still require the runtime checks. Schema string lengths count
Unicode code points; runtime limits may additionally count UTF-16 code units or
UTF-8 bytes.

The [product](examples/product/receipt.json), [job](examples/job/receipt.json), and
[article](examples/article/receipt.json) examples include deterministic version 1
receipts. Tests validate every example receipt against this schema and replay
these fixtures against their local source pages.

## Why grounded-json?

| Typical extraction | grounded-json |
Expand Down
120 changes: 120 additions & 0 deletions examples/article/receipt.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
{
"manifest": {
"format": "grounded-json/receipt",
"version": 1,
"createdAt": "2026-01-01T00:00:00.000Z",
"receiptHash": "sha256:fa319d7cc020b889044b3730bf4cf264082c6cab7ba7f114601ce1fbb7e55924"
},
"data": {
"author": {
"name": "Ada Brooks"
},
"datePublished": "2026-09-01",
"headline": "Why evidence belongs beside data",
"wordCount": 1842
},
"schema": {
"dialect": "https://json-schema.org/draft/2020-12/schema",
"subset": "grounded-json/safe-v1",
"document": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false,
"properties": {
"author": {
"additionalProperties": false,
"properties": {
"name": {
"type": "string"
}
},
"required": ["name"],
"type": "object"
},
"datePublished": {
"type": "string"
},
"headline": {
"type": "string"
},
"wordCount": {
"type": "integer"
}
},
"required": ["headline", "datePublished", "wordCount", "author"],
"title": "Article evidence receipt",
"type": "object"
},
"contentHash": "sha256:a6453ad46d0c702787f9af873e72ecb064fe1a78d1403ec76398c093acc2fab2"
},
"sources": [
{
"id": "source:1150f4282453",
"mediaType": "text/html",
"contentHash": "sha256:1150f4282453f0884bd60e3c14a3038ecb5a5f3ce2e0ae97a57be42e88fc04f9",
"bytes": 577
}
],
"evidence": {
"/author/name": [
{
"pointer": "/author/name",
"quote": "\"Ada Brooks\"",
"selector": "script[type=\"application/ld+json\"]",
"selectorIndex": 0,
"transforms": ["json-decode"],
"sourceId": "source:1150f4282453",
"sourceHash": "sha256:1150f4282453f0884bd60e3c14a3038ecb5a5f3ce2e0ae97a57be42e88fc04f9",
"status": "grounded",
"observedValue": "Ada Brooks"
}
],
"/datePublished": [
{
"pointer": "/datePublished",
"quote": "\"2026-09-01\"",
"selector": "script[type=\"application/ld+json\"]",
"selectorIndex": 0,
"transforms": ["json-decode"],
"sourceId": "source:1150f4282453",
"sourceHash": "sha256:1150f4282453f0884bd60e3c14a3038ecb5a5f3ce2e0ae97a57be42e88fc04f9",
"status": "grounded",
"observedValue": "2026-09-01"
}
],
"/headline": [
{
"pointer": "/headline",
"quote": "\"Why evidence belongs beside data\"",
"selector": "script[type=\"application/ld+json\"]",
"selectorIndex": 0,
"transforms": ["json-decode"],
"sourceId": "source:1150f4282453",
"sourceHash": "sha256:1150f4282453f0884bd60e3c14a3038ecb5a5f3ce2e0ae97a57be42e88fc04f9",
"status": "grounded",
"observedValue": "Why evidence belongs beside data"
}
],
"/wordCount": [
{
"pointer": "/wordCount",
"quote": "1842",
"selector": "script[type=\"application/ld+json\"]",
"selectorIndex": 0,
"transforms": ["json-decode"],
"sourceId": "source:1150f4282453",
"sourceHash": "sha256:1150f4282453f0884bd60e3c14a3038ecb5a5f3ce2e0ae97a57be42e88fc04f9",
"status": "grounded",
"observedValue": 1842
}
]
},
"issues": [],
"coverage": {
"grounded": 4,
"total": 4,
"missing": 0,
"ambiguous": 0,
"invalid": 0,
"ratio": 1
}
}
152 changes: 152 additions & 0 deletions examples/job/receipt.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
{
"manifest": {
"format": "grounded-json/receipt",
"version": 1,
"createdAt": "2026-01-01T00:00:00.000Z",
"receiptHash": "sha256:2577484b9d3ca3fda3f51373f8aa753375dfed080ea35ec36f3c195d53b0aa46"
},
"data": {
"datePosted": "2026-08-28",
"employmentType": "FULL_TIME",
"hiringOrganization": {
"name": "Northwind Systems"
},
"jobLocation": {
"address": "Berlin, Germany"
},
"title": "Senior TypeScript Engineer"
},
"schema": {
"dialect": "https://json-schema.org/draft/2020-12/schema",
"subset": "grounded-json/safe-v1",
"document": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false,
"properties": {
"datePosted": {
"type": "string"
},
"employmentType": {
"type": "string"
},
"hiringOrganization": {
"additionalProperties": false,
"properties": {
"name": {
"type": "string"
}
},
"required": ["name"],
"type": "object"
},
"jobLocation": {
"additionalProperties": false,
"properties": {
"address": {
"type": "string"
}
},
"required": ["address"],
"type": "object"
},
"title": {
"type": "string"
}
},
"required": [
"title",
"datePosted",
"employmentType",
"hiringOrganization",
"jobLocation"
],
"title": "Job evidence receipt",
"type": "object"
},
"contentHash": "sha256:64e34deb977887fad4d0078b6790f5211ded9e23347990c142bc88f9464fd694"
},
"sources": [
{
"id": "source:85ad42bfdc58",
"mediaType": "text/html",
"contentHash": "sha256:85ad42bfdc58e6743d7e5d470d2c066fe2de7ff74972223eba951954496c48df",
"bytes": 690
}
],
"evidence": {
"/datePosted": [
{
"pointer": "/datePosted",
"quote": "\"2026-08-28\"",
"selector": "script[type=\"application/ld+json\"]",
"selectorIndex": 0,
"transforms": ["json-decode"],
"sourceId": "source:85ad42bfdc58",
"sourceHash": "sha256:85ad42bfdc58e6743d7e5d470d2c066fe2de7ff74972223eba951954496c48df",
"status": "grounded",
"observedValue": "2026-08-28"
}
],
"/employmentType": [
{
"pointer": "/employmentType",
"quote": "\"FULL_TIME\"",
"selector": "script[type=\"application/ld+json\"]",
"selectorIndex": 0,
"transforms": ["json-decode"],
"sourceId": "source:85ad42bfdc58",
"sourceHash": "sha256:85ad42bfdc58e6743d7e5d470d2c066fe2de7ff74972223eba951954496c48df",
"status": "grounded",
"observedValue": "FULL_TIME"
}
],
"/hiringOrganization/name": [
{
"pointer": "/hiringOrganization/name",
"quote": "\"Northwind Systems\"",
"selector": "script[type=\"application/ld+json\"]",
"selectorIndex": 0,
"transforms": ["json-decode"],
"sourceId": "source:85ad42bfdc58",
"sourceHash": "sha256:85ad42bfdc58e6743d7e5d470d2c066fe2de7ff74972223eba951954496c48df",
"status": "grounded",
"observedValue": "Northwind Systems"
}
],
"/jobLocation/address": [
{
"pointer": "/jobLocation/address",
"quote": "\"Berlin, Germany\"",
"selector": "script[type=\"application/ld+json\"]",
"selectorIndex": 0,
"transforms": ["json-decode"],
"sourceId": "source:85ad42bfdc58",
"sourceHash": "sha256:85ad42bfdc58e6743d7e5d470d2c066fe2de7ff74972223eba951954496c48df",
"status": "grounded",
"observedValue": "Berlin, Germany"
}
],
"/title": [
{
"pointer": "/title",
"quote": "\"Senior TypeScript Engineer\"",
"selector": "script[type=\"application/ld+json\"]",
"selectorIndex": 0,
"transforms": ["json-decode"],
"sourceId": "source:85ad42bfdc58",
"sourceHash": "sha256:85ad42bfdc58e6743d7e5d470d2c066fe2de7ff74972223eba951954496c48df",
"status": "grounded",
"observedValue": "Senior TypeScript Engineer"
}
]
},
"issues": [],
"coverage": {
"grounded": 5,
"total": 5,
"missing": 0,
"ambiguous": 0,
"invalid": 0,
"ratio": 1
}
}
Loading