@gnolith/taproot 0.1.0-rc.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.
Files changed (52) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/COMPATIBILITY.md +42 -0
  3. package/LICENSE +21 -0
  4. package/README.md +128 -0
  5. package/SECURITY.md +19 -0
  6. package/SUPPORT.md +11 -0
  7. package/dist/canonical.d.ts +16 -0
  8. package/dist/canonical.d.ts.map +1 -0
  9. package/dist/canonical.js +519 -0
  10. package/dist/canonical.js.map +1 -0
  11. package/dist/errors.d.ts +35 -0
  12. package/dist/errors.d.ts.map +1 -0
  13. package/dist/errors.js +42 -0
  14. package/dist/errors.js.map +1 -0
  15. package/dist/index.d.ts +57 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +53 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/iri.d.ts +3 -0
  20. package/dist/iri.d.ts.map +1 -0
  21. package/dist/iri.js +8 -0
  22. package/dist/iri.js.map +1 -0
  23. package/dist/rdf.d.ts +6 -0
  24. package/dist/rdf.d.ts.map +1 -0
  25. package/dist/rdf.js +292 -0
  26. package/dist/rdf.js.map +1 -0
  27. package/dist/repository.d.ts +94 -0
  28. package/dist/repository.d.ts.map +1 -0
  29. package/dist/repository.js +1499 -0
  30. package/dist/repository.js.map +1 -0
  31. package/dist/schema.d.ts +19 -0
  32. package/dist/schema.d.ts.map +1 -0
  33. package/dist/schema.js +390 -0
  34. package/dist/schema.js.map +1 -0
  35. package/dist/types.d.ts +299 -0
  36. package/dist/types.d.ts.map +1 -0
  37. package/dist/types.js +21 -0
  38. package/dist/types.js.map +1 -0
  39. package/docs/api.md +47 -0
  40. package/docs/architecture.md +30 -0
  41. package/docs/completion-audit.md +31 -0
  42. package/docs/operations.md +47 -0
  43. package/docs/product-scope.md +20 -0
  44. package/docs/release-policy.md +16 -0
  45. package/docs/testing.md +21 -0
  46. package/docs/threat-model.md +40 -0
  47. package/examples/codex-site/.openai/hosting.json +3 -0
  48. package/examples/codex-site/README.md +13 -0
  49. package/examples/codex-site/demo.ts +152 -0
  50. package/migrations/0001_taproot.sql +64 -0
  51. package/migrations/0002_audit_operations.sql +48 -0
  52. package/package.json +89 -0
@@ -0,0 +1,152 @@
1
+ import {
2
+ RevisionConflictError,
3
+ TaprootRepository,
4
+ exportEntityJson,
5
+ initializeTaproot,
6
+ type D1DatabaseLike,
7
+ type Statement,
8
+ } from '@gnolith/taproot';
9
+ import { createSparqlHandler } from '@gnolith/diamond';
10
+
11
+ export async function runTaprootDemo(db: D1DatabaseLike) {
12
+ await initializeTaproot(db);
13
+ const taproot = new TaprootRepository(db, {
14
+ baseIri: 'https://knowledge.example',
15
+ });
16
+
17
+ await taproot.createProperty({
18
+ datatype: 'string',
19
+ labels: { en: { language: 'en', value: 'occupation' } },
20
+ });
21
+ await taproot.createProperty({
22
+ datatype: 'time',
23
+ labels: { en: { language: 'en', value: 'point in time' } },
24
+ });
25
+ await taproot.createProperty({
26
+ datatype: 'url',
27
+ labels: { en: { language: 'en', value: 'reference URL' } },
28
+ });
29
+
30
+ const created = await taproot.createItem({
31
+ labels: { en: { language: 'en', value: 'Ada Lovelace' } },
32
+ descriptions: {
33
+ en: { language: 'en', value: 'English mathematician' },
34
+ },
35
+ attribution: {
36
+ id: 'agent:demo-curator',
37
+ kind: 'agent',
38
+ tool: 'gnolith-mcp',
39
+ },
40
+ requestId: 'demo-request',
41
+ });
42
+ const occupation: Statement = {
43
+ id: `${created.entityId}$occupation`,
44
+ type: 'statement',
45
+ rank: 'normal',
46
+ mainsnak: {
47
+ snaktype: 'value',
48
+ property: 'P1',
49
+ datatype: 'string',
50
+ datavalue: { type: 'string', value: 'computer programmer' },
51
+ },
52
+ qualifiers: {
53
+ P2: [
54
+ {
55
+ snaktype: 'value',
56
+ property: 'P2',
57
+ datatype: 'time',
58
+ datavalue: {
59
+ type: 'time',
60
+ value: {
61
+ time: '+1843-01-01T00:00:00Z',
62
+ timezone: 0,
63
+ before: 0,
64
+ after: 0,
65
+ precision: 9,
66
+ calendarmodel: 'http://www.wikidata.org/entity/Q1985727',
67
+ },
68
+ },
69
+ },
70
+ ],
71
+ },
72
+ 'qualifiers-order': ['P2'],
73
+ references: [
74
+ {
75
+ hash: 'demo-source',
76
+ snaks: {
77
+ P3: [
78
+ {
79
+ snaktype: 'value',
80
+ property: 'P3',
81
+ datatype: 'url',
82
+ datavalue: {
83
+ type: 'string',
84
+ value: 'https://example.test/source',
85
+ },
86
+ },
87
+ ],
88
+ },
89
+ 'snaks-order': ['P3'],
90
+ },
91
+ ],
92
+ };
93
+ const added = await taproot.addStatement(created.entityId, occupation, {
94
+ expectedRevision: created.newRevision,
95
+ });
96
+ const preferred = await taproot.setStatementRank(
97
+ created.entityId,
98
+ occupation.id,
99
+ 'preferred',
100
+ { expectedRevision: added.newRevision },
101
+ );
102
+ const unknownOccupation: Statement = {
103
+ id: `${created.entityId}$unknown-occupation`,
104
+ type: 'statement',
105
+ rank: 'normal',
106
+ mainsnak: {
107
+ snaktype: 'somevalue',
108
+ property: 'P1',
109
+ datatype: 'string',
110
+ },
111
+ qualifiers: {},
112
+ 'qualifiers-order': [],
113
+ references: [],
114
+ };
115
+ const special = await taproot.addStatement(
116
+ created.entityId,
117
+ unknownOccupation,
118
+ { expectedRevision: preferred.newRevision },
119
+ );
120
+
121
+ const sparql = createSparqlHandler({ db });
122
+ const query = `SELECT ?occupation WHERE {
123
+ <https://knowledge.example/entity/${created.entityId}>
124
+ <https://knowledge.example/prop/direct/P1> ?occupation
125
+ }`;
126
+ const response = await sparql(
127
+ new Request(
128
+ `https://site.example/api/sparql?query=${encodeURIComponent(query)}`,
129
+ { headers: { accept: 'application/sparql-results+json' } },
130
+ ),
131
+ );
132
+
133
+ let staleRevisionRejected = false;
134
+ try {
135
+ await taproot.setLabel(created.entityId, 'en', 'stale edit', {
136
+ expectedRevision: created.newRevision,
137
+ });
138
+ } catch (cause) {
139
+ staleRevisionRejected = cause instanceof RevisionConflictError;
140
+ }
141
+
142
+ const sparqlResults: unknown = await response.json();
143
+ const audit = await taproot.listAuditEvents({ entityId: created.entityId });
144
+ const integrity = await taproot.inspectEntityIntegrity(created.entityId);
145
+ return {
146
+ entityJson: exportEntityJson(special.entity),
147
+ sparqlResults,
148
+ staleRevisionRejected,
149
+ audit: audit.items,
150
+ integrity,
151
+ };
152
+ }
@@ -0,0 +1,64 @@
1
+ CREATE TABLE taproot_entities (
2
+ entity_id TEXT PRIMARY KEY,
3
+ entity_type TEXT NOT NULL CHECK (entity_type IN ('item', 'property')),
4
+ datatype TEXT,
5
+ revision INTEGER NOT NULL,
6
+ entity_json TEXT NOT NULL CHECK (json_valid(entity_json)),
7
+ created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
8
+ modified_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
9
+ deleted_at TEXT,
10
+ redirect_to TEXT,
11
+ CHECK (
12
+ (entity_type = 'item' AND datatype IS NULL)
13
+ OR (entity_type = 'property' AND datatype IS NOT NULL)
14
+ )
15
+ ) STRICT;
16
+
17
+ CREATE TABLE taproot_entity_revisions (
18
+ entity_id TEXT NOT NULL,
19
+ revision INTEGER NOT NULL,
20
+ entity_json TEXT NOT NULL CHECK (json_valid(entity_json)),
21
+ actor TEXT,
22
+ edit_summary TEXT,
23
+ created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
24
+ PRIMARY KEY (entity_id, revision),
25
+ FOREIGN KEY (entity_id) REFERENCES taproot_entities(entity_id)
26
+ ) STRICT;
27
+
28
+ CREATE TABLE taproot_id_counters (
29
+ entity_type TEXT PRIMARY KEY CHECK (entity_type IN ('item', 'property')),
30
+ next_numeric_id INTEGER NOT NULL CHECK (next_numeric_id > 0)
31
+ ) STRICT;
32
+
33
+ CREATE TABLE taproot_terms (
34
+ entity_id TEXT NOT NULL,
35
+ language TEXT NOT NULL,
36
+ term_type TEXT NOT NULL CHECK (term_type IN ('label', 'description', 'alias')),
37
+ value TEXT NOT NULL,
38
+ ordinal INTEGER NOT NULL DEFAULT 0,
39
+ PRIMARY KEY (entity_id, language, term_type, ordinal),
40
+ FOREIGN KEY (entity_id) REFERENCES taproot_entities(entity_id)
41
+ ) STRICT;
42
+
43
+ CREATE TABLE taproot_metadata (
44
+ metadata_key TEXT PRIMARY KEY,
45
+ metadata_value TEXT NOT NULL
46
+ ) STRICT;
47
+
48
+ CREATE TABLE taproot_assertions (
49
+ assertion_key TEXT PRIMARY KEY
50
+ ) STRICT;
51
+
52
+ CREATE INDEX taproot_entities_type_idx ON taproot_entities(entity_type, entity_id);
53
+ CREATE INDEX taproot_entities_modified_idx ON taproot_entities(modified_at, entity_id);
54
+ CREATE INDEX taproot_revisions_entity_idx ON taproot_entity_revisions(entity_id, revision DESC);
55
+ CREATE INDEX taproot_terms_lookup_idx ON taproot_terms(language, value COLLATE NOCASE, entity_id);
56
+
57
+ INSERT INTO taproot_id_counters(entity_type, next_numeric_id)
58
+ VALUES ('item', 1), ('property', 1);
59
+
60
+ INSERT INTO taproot_metadata(metadata_key, metadata_value)
61
+ VALUES
62
+ ('schema_version', '1'),
63
+ ('canonical_json_version', '1'),
64
+ ('rdf_mapping_version', '1');
@@ -0,0 +1,48 @@
1
+ -- Structural half of the v1 -> v2 migration. initializeTaproot() must run
2
+ -- immediately afterward to SHA-256 backfill legacy revisions, create their
3
+ -- audit events, install immutability triggers, and verify version metadata.
4
+ ALTER TABLE taproot_entity_revisions ADD COLUMN attribution_json TEXT CHECK (attribution_json IS NULL OR json_valid(attribution_json));
5
+ ALTER TABLE taproot_entity_revisions ADD COLUMN tags_json TEXT NOT NULL DEFAULT '[]' CHECK (json_valid(tags_json));
6
+ ALTER TABLE taproot_entity_revisions ADD COLUMN event_id TEXT;
7
+ ALTER TABLE taproot_entity_revisions ADD COLUMN content_hash TEXT;
8
+ ALTER TABLE taproot_entity_revisions ADD COLUMN parent_hash TEXT;
9
+ ALTER TABLE taproot_entity_revisions ADD COLUMN deleted_at TEXT;
10
+ ALTER TABLE taproot_entity_revisions ADD COLUMN redirect_to TEXT;
11
+
12
+ CREATE TABLE taproot_audit_events (
13
+ event_id TEXT PRIMARY KEY,
14
+ entity_id TEXT NOT NULL,
15
+ revision INTEGER NOT NULL,
16
+ event_type TEXT NOT NULL CHECK (event_type IN ('create', 'update', 'revert', 'delete', 'restore', 'redirect', 'import', 'repair')),
17
+ attribution_json TEXT CHECK (attribution_json IS NULL OR json_valid(attribution_json)),
18
+ edit_summary TEXT,
19
+ tags_json TEXT NOT NULL DEFAULT '[]' CHECK (json_valid(tags_json)),
20
+ request_id TEXT,
21
+ content_hash TEXT NOT NULL,
22
+ parent_hash TEXT,
23
+ details_json TEXT NOT NULL DEFAULT '{}' CHECK (json_valid(details_json)),
24
+ created_at TEXT NOT NULL,
25
+ FOREIGN KEY (entity_id, revision) REFERENCES taproot_entity_revisions(entity_id, revision)
26
+ ) STRICT;
27
+
28
+ CREATE TABLE taproot_migrations (
29
+ version INTEGER PRIMARY KEY,
30
+ name TEXT NOT NULL,
31
+ applied_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
32
+ ) STRICT;
33
+
34
+ CREATE TABLE taproot_rdf_ownership (
35
+ entity_id TEXT NOT NULL,
36
+ subject_key TEXT NOT NULL,
37
+ predicate_key TEXT NOT NULL,
38
+ object_key TEXT NOT NULL,
39
+ graph_key TEXT NOT NULL,
40
+ PRIMARY KEY (entity_id, subject_key, predicate_key, object_key, graph_key),
41
+ FOREIGN KEY (entity_id) REFERENCES taproot_entities(entity_id)
42
+ ) STRICT;
43
+
44
+ CREATE INDEX taproot_audit_entity_idx ON taproot_audit_events(entity_id, revision DESC, event_id);
45
+ CREATE INDEX taproot_audit_request_idx ON taproot_audit_events(request_id) WHERE request_id IS NOT NULL;
46
+ CREATE INDEX taproot_rdf_ownership_quad_idx ON taproot_rdf_ownership(subject_key, predicate_key, object_key, graph_key, entity_id);
47
+
48
+ INSERT INTO taproot_migrations(version, name) VALUES (1, 'initial'), (2, 'audit-and-operations');
package/package.json ADDED
@@ -0,0 +1,89 @@
1
+ {
2
+ "name": "@gnolith/taproot",
3
+ "version": "0.1.0-rc.0",
4
+ "description": "D1-native Wikibase-compatible knowledge graph and revision layer for Gnolith.",
5
+ "author": "Gnolith contributors",
6
+ "keywords": [
7
+ "cloudflare-d1",
8
+ "knowledge-graph",
9
+ "rdf",
10
+ "sparql",
11
+ "wikibase"
12
+ ],
13
+ "type": "module",
14
+ "private": false,
15
+ "sideEffects": false,
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/gnolith/taproot.git"
19
+ },
20
+ "homepage": "https://github.com/gnolith/taproot#readme",
21
+ "bugs": {
22
+ "url": "https://github.com/gnolith/taproot/issues"
23
+ },
24
+ "publishConfig": {
25
+ "access": "public",
26
+ "provenance": true
27
+ },
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/index.d.ts",
31
+ "import": "./dist/index.js"
32
+ },
33
+ "./migrations/*": "./migrations/*",
34
+ "./package.json": "./package.json"
35
+ },
36
+ "files": [
37
+ "dist",
38
+ "docs",
39
+ "migrations",
40
+ "examples",
41
+ "COMPATIBILITY.md",
42
+ "CHANGELOG.md",
43
+ "README.md",
44
+ "LICENSE",
45
+ "SECURITY.md",
46
+ "SUPPORT.md"
47
+ ],
48
+ "scripts": {
49
+ "build": "tsc -p tsconfig.build.json",
50
+ "check": "npm run format:check && npm run lint && npm run typecheck && npm run test:coverage && npm run build && npm run example:check && npm run pack:check && npm run consumer:check && npm run licenses:check && npm run readiness:check",
51
+ "consumer:check": "node scripts/consumer-smoke.mjs",
52
+ "example:check": "vitest run test/example.test.ts",
53
+ "format": "prettier --write .",
54
+ "format:check": "prettier --check .",
55
+ "licenses:check": "license-checker-rseidelsohn --production --summary --onlyAllow \"MIT;ISC;BSD-2-Clause;Apache-2.0;BSD-3-Clause;BlueOak-1.0.0;FreeBSD;(WTFPL OR MIT);UNLICENSED\"",
56
+ "lint": "eslint .",
57
+ "pack:check": "npm pack --dry-run",
58
+ "prepack": "npm run build",
59
+ "readiness:check": "node scripts/readiness-check.mjs",
60
+ "release:check": "node scripts/release-check.mjs",
61
+ "test": "vitest run",
62
+ "test:coverage": "vitest run --coverage",
63
+ "test:integration": "vitest run test/repository.test.ts test/example.test.ts",
64
+ "test:unit": "vitest run test/rdf.test.ts test/scaffold.test.ts",
65
+ "typecheck": "tsc --noEmit"
66
+ },
67
+ "engines": {
68
+ "node": ">=22"
69
+ },
70
+ "dependencies": {
71
+ "@gnolith/diamond": "^0.3.2",
72
+ "@rdfjs/types": "^2.0.1",
73
+ "rdf-data-factory": "^2.0.2"
74
+ },
75
+ "packageManager": "npm@11.9.0",
76
+ "devDependencies": {
77
+ "@eslint/js": "^10.0.1",
78
+ "@types/node": "^24.0.0",
79
+ "@vitest/coverage-v8": "^3.2.7",
80
+ "eslint": "^10.7.0",
81
+ "license-checker-rseidelsohn": "^4.4.2",
82
+ "miniflare": "^4.20260714.0",
83
+ "prettier": "^3.9.5",
84
+ "typescript": "^5.9.2",
85
+ "typescript-eslint": "^8.64.0",
86
+ "vitest": "^3.2.4"
87
+ },
88
+ "license": "MIT"
89
+ }