@gnolith/taproot 0.1.0-rc.0 → 0.3.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 +63 -0
- package/COMPATIBILITY.md +15 -3
- package/LICENSE +21 -21
- package/README.md +122 -46
- package/SECURITY.md +10 -3
- package/SUPPORT.md +6 -1
- package/dist/authorization-maintenance.d.ts +55 -0
- package/dist/authorization-maintenance.d.ts.map +1 -0
- package/dist/authorization-maintenance.js +686 -0
- package/dist/authorization-maintenance.js.map +1 -0
- package/dist/authorization.d.ts +98 -0
- package/dist/authorization.d.ts.map +1 -0
- package/dist/authorization.js +1137 -0
- package/dist/authorization.js.map +1 -0
- package/dist/canonical.d.ts +2 -1
- package/dist/canonical.d.ts.map +1 -1
- package/dist/canonical.js +9 -1
- package/dist/canonical.js.map +1 -1
- package/dist/errors.d.ts +10 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +10 -0
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +127 -50
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +352 -44
- package/dist/index.js.map +1 -1
- package/dist/migrations.d.ts +42 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +550 -0
- package/dist/migrations.js.map +1 -0
- package/dist/rdf.js +1 -1
- package/dist/rdf.js.map +1 -1
- package/dist/repository.d.ts +22 -11
- package/dist/repository.d.ts.map +1 -1
- package/dist/repository.js +596 -63
- package/dist/repository.js.map +1 -1
- package/dist/schema.d.ts +41 -7
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +722 -83
- package/dist/schema.js.map +1 -1
- package/dist/types.d.ts +59 -0
- package/dist/types.d.ts.map +1 -1
- package/docs/api.md +70 -18
- package/docs/architecture.md +21 -8
- package/docs/authorization.md +82 -0
- package/docs/completion-audit.md +22 -16
- package/docs/operations.md +48 -9
- package/docs/product-scope.md +11 -4
- package/docs/release-policy.md +4 -0
- package/docs/testing.md +31 -5
- package/docs/threat-model.md +38 -6
- package/examples/d1-diamond-interop/README.md +12 -0
- package/examples/d1-diamond-interop/demo.ts +281 -0
- package/migrations/0002_audit_operations.sql +4 -3
- package/migrations/0003_canonical_statement_text.sql +31 -0
- package/migrations/0004_canonical_authorization_policy.sql +213 -0
- package/package.json +12 -7
- package/examples/codex-site/.openai/hosting.json +0 -3
- package/examples/codex-site/README.md +0 -13
- package/examples/codex-site/demo.ts +0 -152
package/docs/product-scope.md
CHANGED
|
@@ -1,20 +1,27 @@
|
|
|
1
1
|
# Product scope
|
|
2
2
|
|
|
3
|
-
Taproot exists so a
|
|
3
|
+
Taproot exists so a D1-backed application can own a Wikibase-style knowledge graph
|
|
4
4
|
and query it through Diamond without operating MediaWiki or another external
|
|
5
5
|
knowledge service.
|
|
6
6
|
|
|
7
|
-
Version 0.
|
|
7
|
+
Version 0.3 covers the package's declared entity domain: Wikibase
|
|
8
8
|
core Items and Properties, canonical JSON interchange, statements and all
|
|
9
9
|
their components, supported datatypes, revision/lifecycle history,
|
|
10
10
|
attribution and audit, search, RDF/SPARQL projection, bulk workflows,
|
|
11
|
-
migration, integrity, repair, policy
|
|
11
|
+
migration, integrity, repair, canonical entity authorization policy, and
|
|
12
|
+
operational documentation.
|
|
12
13
|
|
|
13
14
|
The following are separate products or future compatibility domains rather
|
|
14
15
|
than missing Taproot implementation: MCP transport, agent orchestration,
|
|
15
|
-
authentication/
|
|
16
|
+
authentication, principal/membership/session management, UI, wiki pages,
|
|
17
|
+
media bytes, MediaWiki APIs,
|
|
16
18
|
Lexeme/Form/Sense, MediaInfo, and EntitySchema entity storage.
|
|
17
19
|
|
|
20
|
+
Complete-Site assembly, resource provisioning, remote migration execution,
|
|
21
|
+
deployment, and production acceptance are also outside this package. The Codex
|
|
22
|
+
agent creating a Gnolith Site owns those responsibilities. Taproot qualifies
|
|
23
|
+
only its published package and supported local runtime contracts.
|
|
24
|
+
|
|
18
25
|
Adding an extension entity type changes IDs, JSON, commands, RDF, migration,
|
|
19
26
|
and compatibility fixtures. It must be designed and versioned as an explicit
|
|
20
27
|
new domain rather than accepted as unvalidated JSON.
|
package/docs/release-policy.md
CHANGED
|
@@ -11,6 +11,10 @@ inspects the packed artifact, publishes to npm through OIDC trusted publishing,
|
|
|
11
11
|
and attaches the tarball, SBOM, checksums, and build attestation to a GitHub
|
|
12
12
|
Release.
|
|
13
13
|
|
|
14
|
+
A Taproot release qualifies the published package and its supported local
|
|
15
|
+
runtime contracts. It does not assemble, provision, deploy, or accept a complete
|
|
16
|
+
Gnolith Site; the Codex agent creating a Site owns those responsibilities.
|
|
17
|
+
|
|
14
18
|
The current minor line receives fixes. Security support follows `SECURITY.md`.
|
|
15
19
|
Deprecations are announced in the changelog before removal whenever a safe
|
|
16
20
|
migration path exists.
|
package/docs/testing.md
CHANGED
|
@@ -11,11 +11,37 @@ projection repair, shared RDF values, and injected RDF failures. The injected
|
|
|
11
11
|
failure proves that canonical JSON, revisions, audit events, search terms,
|
|
12
12
|
ownership, and Diamond quads roll back as one D1 batch.
|
|
13
13
|
|
|
14
|
-
`test/
|
|
15
|
-
Taproot-generated graph through Diamond SPARQL.
|
|
14
|
+
`test/interop.test.ts` runs the documented local D1 and Diamond interoperability
|
|
15
|
+
example and queries its Taproot-generated graph through Diamond SPARQL.
|
|
16
|
+
|
|
17
|
+
`test/persistence.test.ts` exercises Diamond's Node SQLite adapter in memory
|
|
18
|
+
and on disk, including read-only planning, immutable identity, close/reopen,
|
|
19
|
+
same-adapter enforcement, late-batch rollback, and concurrent writers.
|
|
20
|
+
`consumer:check` packs the
|
|
16
21
|
package, installs the tarball into a fresh temporary project with registry
|
|
17
22
|
Diamond and Miniflare dependencies, initializes a new D1 database, writes an
|
|
18
|
-
entity, and verifies the RDF through SPARQL using only
|
|
23
|
+
entity, and verifies the RDF through privileged package-test SPARQL using only
|
|
24
|
+
public package exports. This proves RDF interoperability, not authorization of
|
|
25
|
+
a user-facing SPARQL endpoint.
|
|
26
|
+
|
|
27
|
+
`npm run check` is the complete local package release-quality gate. Coverage
|
|
28
|
+
thresholds are enforced in `vitest.config.ts`; lowering them requires an
|
|
29
|
+
explained review. Miniflare/Workerd and packed-consumer success verifies the
|
|
30
|
+
package's supported D1 runtime contract, not assembly, provisioning, deployment,
|
|
31
|
+
or acceptance of a complete Gnolith Site. The Codex agent creating a Site owns
|
|
32
|
+
those checks.
|
|
33
|
+
|
|
34
|
+
## Miniflare dependency security
|
|
35
|
+
|
|
36
|
+
Miniflare is a development-only dependency used to qualify Taproot against its
|
|
37
|
+
supported Workerd D1 runtime. Taproot applies an exact root override of
|
|
38
|
+
`sharp@0.35.3` because Miniflare currently declares `sharp@0.34.5`, which is
|
|
39
|
+
affected by GHSA-f88m-g3jw-g9cj. This is a temporary upstream-compatibility
|
|
40
|
+
exception tracked in [Cloudflare workers-sdk PR #14493](https://github.com/cloudflare/workers-sdk/pull/14493).
|
|
41
|
+
Remove the override once a Miniflare release in Taproot's supported range
|
|
42
|
+
declares a non-vulnerable Sharp version and the full D1, interop,
|
|
43
|
+
packed-consumer, Node 22/24, and audit gates pass without it.
|
|
19
44
|
|
|
20
|
-
|
|
21
|
-
|
|
45
|
+
The override is qualified for Taproot's Miniflare D1 test use. It is not a claim
|
|
46
|
+
of general Cloudflare Images compatibility, nor a compatibility claim for
|
|
47
|
+
macOS, ARM, or musl environments.
|
package/docs/threat-model.md
CHANGED
|
@@ -8,9 +8,19 @@ association between an edit and its claimed attribution metadata.
|
|
|
8
8
|
|
|
9
9
|
## Trust boundaries
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
Taproot assumes its consumer supplies a SQLite/D1 binding and absolute base IRI.
|
|
12
|
+
Hosts create `AuthorizationContext` values only from authenticated state;
|
|
13
|
+
Taproot loads persisted canonical policies and checks them before and after
|
|
14
|
+
hydration. The normal package export has no raw canonical repository. Every
|
|
15
|
+
public mutation requires a runtime-branded installation guard, a current
|
|
16
|
+
context with exact `knowledge:write`, current target visibility, and canonical
|
|
17
|
+
policy. Policy changes require exact `knowledge:policy`. Invalid guards or
|
|
18
|
+
contexts fail before database work or observers. Mutation receipts do not
|
|
19
|
+
disclose canonical bodies. Host assembly withholds the database binding,
|
|
20
|
+
host capability, guard, and keys from request, user, agent, and MCP code. The
|
|
21
|
+
host capability is bootstrap/maintenance authority, while ordinary mutations
|
|
22
|
+
remain per-entity policy authorized.
|
|
23
|
+
Attribution is a stored claim, not proof of identity. Diamond executes
|
|
14
24
|
the RDF patch prepared by Taproot and exposes read-only SPARQL querying; hosts
|
|
15
25
|
must not give untrusted callers an independent write path to Taproot or Diamond
|
|
16
26
|
tables.
|
|
@@ -19,22 +29,44 @@ tables.
|
|
|
19
29
|
|
|
20
30
|
- Lost updates are blocked by expected-revision guards inside the same D1 batch
|
|
21
31
|
as all canonical and projected writes.
|
|
32
|
+
- Authorization ABA races are blocked by a shared counter CAS plus a unique
|
|
33
|
+
durable advance ID asserted inside the same ordered batch.
|
|
22
34
|
- Partial writes are blocked by D1 batch atomicity; injected Workerd failures
|
|
23
35
|
verify rollback across Taproot and Diamond.
|
|
24
36
|
- History tampering is deterred and detected by immutable-table triggers,
|
|
25
37
|
SHA-256 content/parent chains, schema inspection, and integrity APIs.
|
|
26
38
|
- Resource exhaustion is bounded by entity, command, page, bulk, redirect, and
|
|
27
|
-
RDF patch limits.
|
|
28
|
-
|
|
39
|
+
RDF patch limits. Request-size, authentication, and network rate limits are
|
|
40
|
+
outside the package boundary.
|
|
29
41
|
- RDF projection confusion is constrained by strict entity validation,
|
|
30
42
|
deterministic mapping, site-owned namespaces, and per-entity quad ownership.
|
|
43
|
+
- Application/search canonical reads fail closed on absent policy,
|
|
44
|
+
cross-installation identity, stale authorization revision, policy changes
|
|
45
|
+
during hydration, and malformed CNF visibility. Denials have a generic body.
|
|
46
|
+
- Search administration requires the exact `search:admin` capability; personas
|
|
47
|
+
and generic administrator labels do not imply it.
|
|
48
|
+
- Page cursors require a non-extractable host AES-GCM key, use fixed-size
|
|
49
|
+
plaintext padding, and bind caller, grants, operation, query/filter, auth
|
|
50
|
+
revision, and search generation.
|
|
31
51
|
- Dependency and release compromise is reduced through locked installs, pinned
|
|
32
52
|
GitHub Actions, dependency review, CodeQL, secret scanning, license checks,
|
|
33
53
|
OIDC npm publishing, provenance, SBOMs, checksums, and attestations.
|
|
34
54
|
|
|
35
55
|
## Out of scope
|
|
36
56
|
|
|
37
|
-
Taproot does not provide authentication,
|
|
57
|
+
Taproot does not provide authentication, principal/workspace persistence, agent/MCP isolation,
|
|
38
58
|
network rate limiting, D1 backup custody, UI sanitization, or arbitrary SPARQL
|
|
39
59
|
Update. A compromised host with direct D1 access can bypass library controls;
|
|
40
60
|
restore from a trusted backup and verify audit chains after such an incident.
|
|
61
|
+
|
|
62
|
+
Taproot's local Workerd tests validate its package controls under the supported
|
|
63
|
+
D1 contract. They do not qualify a deployed host. The Codex agent creating a
|
|
64
|
+
Site owns assembly, provisioning, route and security wiring, deployment, backup
|
|
65
|
+
policy, and production acceptance.
|
|
66
|
+
|
|
67
|
+
Diamond's database-level SPARQL handler sees Taproot's complete RDF projection
|
|
68
|
+
and is therefore privileged host maintenance/debug infrastructure, not a
|
|
69
|
+
normal user, agent, MCP, or search endpoint. Workshop must construct an
|
|
70
|
+
authorization-scoped dataset (with a final canonical recheck) before exposing
|
|
71
|
+
SPARQL results. Possession of the SQLite/D1 binding itself remains privileged:
|
|
72
|
+
a compromised host can query tables outside any package API.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Local D1 and Diamond interoperability example
|
|
2
|
+
|
|
3
|
+
Pass a D1-compatible database to `runTaprootInteropDemo(db)`. The demo
|
|
4
|
+
initializes Taproot and Diamond in the same database, creates Properties and an
|
|
5
|
+
Item, applies revision-guarded edits, and queries Taproot's RDF projection
|
|
6
|
+
through Diamond SPARQL. The SPARQL call is a package interoperability probe
|
|
7
|
+
over a privileged database binding, not an authorization-safe endpoint.
|
|
8
|
+
|
|
9
|
+
The automated test runs this example locally with Miniflare's Workerd D1
|
|
10
|
+
implementation. It verifies the published package's D1 and Diamond contracts;
|
|
11
|
+
it does not assemble, provision, deploy, or accept a complete Gnolith Site.
|
|
12
|
+
Those responsibilities belong to the Codex agent creating that Site.
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RevisionConflictError,
|
|
3
|
+
SEARCH_ADMIN_CAPABILITY,
|
|
4
|
+
KNOWLEDGE_WRITE_CAPABILITY,
|
|
5
|
+
KNOWLEDGE_POLICY_CAPABILITY,
|
|
6
|
+
addStatement,
|
|
7
|
+
bootstrapTaprootAuthorization,
|
|
8
|
+
createAuthorizationCursorCodec,
|
|
9
|
+
createAuthorizedTaproot,
|
|
10
|
+
createItem,
|
|
11
|
+
createProperty,
|
|
12
|
+
createTaprootHostWriteCapability,
|
|
13
|
+
createInstallationAuthorizationGuard,
|
|
14
|
+
exportEntityJson,
|
|
15
|
+
initializeTaproot,
|
|
16
|
+
setLabel,
|
|
17
|
+
setStatementRank,
|
|
18
|
+
type CanonicalAuthorizationPolicyInput,
|
|
19
|
+
type D1DatabaseLike,
|
|
20
|
+
type Statement,
|
|
21
|
+
type AuthorizationContext,
|
|
22
|
+
} from '@gnolith/taproot';
|
|
23
|
+
import { createSparqlHandler } from '@gnolith/diamond';
|
|
24
|
+
|
|
25
|
+
export async function runTaprootInteropDemo(db: D1DatabaseLike) {
|
|
26
|
+
await initializeTaproot(db, { baseIri: 'https://knowledge.example' });
|
|
27
|
+
const options = { baseIri: 'https://knowledge.example' };
|
|
28
|
+
const writeCapability = createTaprootHostWriteCapability(
|
|
29
|
+
db,
|
|
30
|
+
options,
|
|
31
|
+
await crypto.subtle.generateKey({ name: 'HMAC', hash: 'SHA-256' }, false, [
|
|
32
|
+
'sign',
|
|
33
|
+
]),
|
|
34
|
+
);
|
|
35
|
+
await bootstrapTaprootAuthorization(
|
|
36
|
+
db,
|
|
37
|
+
options,
|
|
38
|
+
writeCapability,
|
|
39
|
+
'demo-installation',
|
|
40
|
+
);
|
|
41
|
+
const authorizationGuard = await createInstallationAuthorizationGuard(
|
|
42
|
+
db,
|
|
43
|
+
options,
|
|
44
|
+
writeCapability,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
await createProperty(db, options, authorizationGuard, writer(1), {
|
|
48
|
+
datatype: 'string',
|
|
49
|
+
labels: { en: { language: 'en', value: 'occupation' } },
|
|
50
|
+
authorization: policy(1),
|
|
51
|
+
});
|
|
52
|
+
await createProperty(db, options, authorizationGuard, writer(2), {
|
|
53
|
+
datatype: 'time',
|
|
54
|
+
labels: { en: { language: 'en', value: 'point in time' } },
|
|
55
|
+
authorization: policy(2),
|
|
56
|
+
});
|
|
57
|
+
await createProperty(db, options, authorizationGuard, writer(3), {
|
|
58
|
+
datatype: 'url',
|
|
59
|
+
labels: { en: { language: 'en', value: 'reference URL' } },
|
|
60
|
+
authorization: policy(3),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const created = await createItem(db, options, authorizationGuard, writer(4), {
|
|
64
|
+
labels: { en: { language: 'en', value: 'Ada Lovelace' } },
|
|
65
|
+
descriptions: {
|
|
66
|
+
en: { language: 'en', value: 'English mathematician' },
|
|
67
|
+
},
|
|
68
|
+
attribution: {
|
|
69
|
+
id: 'agent:demo-curator',
|
|
70
|
+
kind: 'agent',
|
|
71
|
+
tool: 'gnolith-mcp',
|
|
72
|
+
},
|
|
73
|
+
requestId: 'demo-request',
|
|
74
|
+
authorization: policy(4),
|
|
75
|
+
});
|
|
76
|
+
const occupation: Statement = {
|
|
77
|
+
id: `${created.entityId}$occupation`,
|
|
78
|
+
type: 'statement',
|
|
79
|
+
text: 'Ada Lovelace worked as a computer programmer in 1843.',
|
|
80
|
+
rank: 'normal',
|
|
81
|
+
mainsnak: {
|
|
82
|
+
snaktype: 'value',
|
|
83
|
+
property: 'P1',
|
|
84
|
+
datatype: 'string',
|
|
85
|
+
datavalue: { type: 'string', value: 'computer programmer' },
|
|
86
|
+
},
|
|
87
|
+
qualifiers: {
|
|
88
|
+
P2: [
|
|
89
|
+
{
|
|
90
|
+
snaktype: 'value',
|
|
91
|
+
property: 'P2',
|
|
92
|
+
datatype: 'time',
|
|
93
|
+
datavalue: {
|
|
94
|
+
type: 'time',
|
|
95
|
+
value: {
|
|
96
|
+
time: '+1843-01-01T00:00:00Z',
|
|
97
|
+
timezone: 0,
|
|
98
|
+
before: 0,
|
|
99
|
+
after: 0,
|
|
100
|
+
precision: 9,
|
|
101
|
+
calendarmodel: 'http://www.wikidata.org/entity/Q1985727',
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
'qualifiers-order': ['P2'],
|
|
108
|
+
references: [
|
|
109
|
+
{
|
|
110
|
+
hash: 'demo-source',
|
|
111
|
+
snaks: {
|
|
112
|
+
P3: [
|
|
113
|
+
{
|
|
114
|
+
snaktype: 'value',
|
|
115
|
+
property: 'P3',
|
|
116
|
+
datatype: 'url',
|
|
117
|
+
datavalue: {
|
|
118
|
+
type: 'string',
|
|
119
|
+
value: 'https://example.test/source',
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
},
|
|
124
|
+
'snaks-order': ['P3'],
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
};
|
|
128
|
+
const added = await addStatement(
|
|
129
|
+
db,
|
|
130
|
+
options,
|
|
131
|
+
authorizationGuard,
|
|
132
|
+
writer(5, true),
|
|
133
|
+
created.entityId,
|
|
134
|
+
occupation,
|
|
135
|
+
{
|
|
136
|
+
expectedRevision: created.newRevision,
|
|
137
|
+
authorization: policy(5, { [occupation.id]: [] }),
|
|
138
|
+
},
|
|
139
|
+
);
|
|
140
|
+
const preferred = await setStatementRank(
|
|
141
|
+
db,
|
|
142
|
+
options,
|
|
143
|
+
authorizationGuard,
|
|
144
|
+
writer(6),
|
|
145
|
+
created.entityId,
|
|
146
|
+
occupation.id,
|
|
147
|
+
'preferred',
|
|
148
|
+
'Ada Lovelace worked as a computer programmer in 1843 (preferred).',
|
|
149
|
+
{
|
|
150
|
+
expectedRevision: added.newRevision,
|
|
151
|
+
authorization: policy(6, { [occupation.id]: [] }),
|
|
152
|
+
},
|
|
153
|
+
);
|
|
154
|
+
const unknownOccupation: Statement = {
|
|
155
|
+
id: `${created.entityId}$unknown-occupation`,
|
|
156
|
+
type: 'statement',
|
|
157
|
+
text: 'Ada Lovelace had another occupation whose value is unknown.',
|
|
158
|
+
rank: 'normal',
|
|
159
|
+
mainsnak: {
|
|
160
|
+
snaktype: 'somevalue',
|
|
161
|
+
property: 'P1',
|
|
162
|
+
datatype: 'string',
|
|
163
|
+
},
|
|
164
|
+
qualifiers: {},
|
|
165
|
+
'qualifiers-order': [],
|
|
166
|
+
references: [],
|
|
167
|
+
};
|
|
168
|
+
await addStatement(
|
|
169
|
+
db,
|
|
170
|
+
options,
|
|
171
|
+
authorizationGuard,
|
|
172
|
+
writer(7, true),
|
|
173
|
+
created.entityId,
|
|
174
|
+
unknownOccupation,
|
|
175
|
+
{
|
|
176
|
+
expectedRevision: preferred.newRevision,
|
|
177
|
+
authorization: policy(7, {
|
|
178
|
+
[occupation.id]: [],
|
|
179
|
+
[unknownOccupation.id]: [],
|
|
180
|
+
}),
|
|
181
|
+
},
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
const sparql = createSparqlHandler({ db });
|
|
185
|
+
const query = `SELECT ?occupation WHERE {
|
|
186
|
+
<https://knowledge.example/entity/${created.entityId}>
|
|
187
|
+
<https://knowledge.example/prop/direct/P1> ?occupation
|
|
188
|
+
}`;
|
|
189
|
+
const response = await sparql(
|
|
190
|
+
new Request(
|
|
191
|
+
`https://interop.example/sparql?query=${encodeURIComponent(query)}`,
|
|
192
|
+
{ headers: { accept: 'application/sparql-results+json' } },
|
|
193
|
+
),
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
let staleRevisionRejected = false;
|
|
197
|
+
try {
|
|
198
|
+
await setLabel(
|
|
199
|
+
db,
|
|
200
|
+
options,
|
|
201
|
+
authorizationGuard,
|
|
202
|
+
writer(8),
|
|
203
|
+
created.entityId,
|
|
204
|
+
'en',
|
|
205
|
+
'stale edit',
|
|
206
|
+
{
|
|
207
|
+
expectedRevision: created.newRevision,
|
|
208
|
+
authorization: policy(8, {
|
|
209
|
+
[occupation.id]: [],
|
|
210
|
+
[unknownOccupation.id]: [],
|
|
211
|
+
}),
|
|
212
|
+
},
|
|
213
|
+
);
|
|
214
|
+
} catch (cause) {
|
|
215
|
+
staleRevisionRejected = cause instanceof RevisionConflictError;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const sparqlResults: unknown = await response.json();
|
|
219
|
+
const reader = createAuthorizedTaproot(
|
|
220
|
+
db,
|
|
221
|
+
options,
|
|
222
|
+
{
|
|
223
|
+
installationId: 'demo-installation',
|
|
224
|
+
principalId: 'demo-principal',
|
|
225
|
+
activeWorkspaceId: null,
|
|
226
|
+
workspaceIds: [],
|
|
227
|
+
capabilities: [SEARCH_ADMIN_CAPABILITY],
|
|
228
|
+
authorizationRevision: 8,
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
cursorCodec: createAuthorizationCursorCodec(
|
|
232
|
+
await crypto.subtle.generateKey(
|
|
233
|
+
{ name: 'AES-GCM', length: 256 },
|
|
234
|
+
false,
|
|
235
|
+
['encrypt', 'decrypt'],
|
|
236
|
+
),
|
|
237
|
+
),
|
|
238
|
+
},
|
|
239
|
+
);
|
|
240
|
+
const audit = await reader.listAuditEvents({ entityId: created.entityId });
|
|
241
|
+
const integrity = await reader.inspectEntityIntegrity(created.entityId);
|
|
242
|
+
const canonical = await reader.getEntity(created.entityId);
|
|
243
|
+
return {
|
|
244
|
+
entityJson: exportEntityJson(canonical.entity),
|
|
245
|
+
sparqlResults,
|
|
246
|
+
staleRevisionRejected,
|
|
247
|
+
audit: audit.items,
|
|
248
|
+
integrity,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function policy(
|
|
253
|
+
expectedAuthorizationRevision: number,
|
|
254
|
+
statementRestrictions: CanonicalAuthorizationPolicyInput['statementRestrictions'] = {},
|
|
255
|
+
): CanonicalAuthorizationPolicyInput {
|
|
256
|
+
return {
|
|
257
|
+
installationId: 'demo-installation',
|
|
258
|
+
workspaceId: null,
|
|
259
|
+
ownerPrincipalId: 'demo-principal',
|
|
260
|
+
visibility: { version: 1, clauses: [] },
|
|
261
|
+
statementRestrictions,
|
|
262
|
+
expectedAuthorizationRevision,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function writer(
|
|
267
|
+
authorizationRevision: number,
|
|
268
|
+
policyAuthority = false,
|
|
269
|
+
): AuthorizationContext {
|
|
270
|
+
return {
|
|
271
|
+
installationId: 'demo-installation',
|
|
272
|
+
principalId: 'demo-principal',
|
|
273
|
+
activeWorkspaceId: null,
|
|
274
|
+
workspaceIds: [],
|
|
275
|
+
capabilities: [
|
|
276
|
+
KNOWLEDGE_WRITE_CAPABILITY,
|
|
277
|
+
...(policyAuthority ? [KNOWLEDGE_POLICY_CAPABILITY] : []),
|
|
278
|
+
],
|
|
279
|
+
authorizationRevision,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
--
|
|
2
|
-
--
|
|
3
|
-
--
|
|
1
|
+
-- Historical 0.1 structural migration retained as package documentation.
|
|
2
|
+
-- Taproot 0.2 hosts must use the exported migration/initializer APIs rather
|
|
3
|
+
-- than applying this file directly; checksummed adoption and JavaScript
|
|
4
|
+
-- backfills cannot be represented by this SQL file alone.
|
|
4
5
|
ALTER TABLE taproot_entity_revisions ADD COLUMN attribution_json TEXT CHECK (attribution_json IS NULL OR json_valid(attribution_json));
|
|
5
6
|
ALTER TABLE taproot_entity_revisions ADD COLUMN tags_json TEXT NOT NULL DEFAULT '[]' CHECK (json_valid(tags_json));
|
|
6
7
|
ALTER TABLE taproot_entity_revisions ADD COLUMN event_id TEXT;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
-- Documentation copy of canonical JSON migration 3.
|
|
2
|
+
-- Hosts must use applyTaprootMigrations() so the package migration ledger is
|
|
3
|
+
-- updated atomically. No fallback statement prose is generated.
|
|
4
|
+
INSERT INTO taproot_assertions(assertion_key)
|
|
5
|
+
WITH canonical_json(entity_json) AS (
|
|
6
|
+
SELECT entity_json FROM taproot_entities
|
|
7
|
+
UNION ALL
|
|
8
|
+
SELECT entity_json FROM taproot_entity_revisions
|
|
9
|
+
)
|
|
10
|
+
SELECT NULL
|
|
11
|
+
FROM canonical_json AS source,
|
|
12
|
+
json_each(source.entity_json, '$.claims') AS claim,
|
|
13
|
+
json_each(claim.value) AS statement
|
|
14
|
+
WHERE json_type(statement.value, '$.text') IS NOT 'text'
|
|
15
|
+
OR trim(json_extract(statement.value, '$.text')) = ''
|
|
16
|
+
LIMIT 1;
|
|
17
|
+
|
|
18
|
+
UPDATE taproot_metadata SET metadata_value = '2'
|
|
19
|
+
WHERE metadata_key = 'canonical_json_version'
|
|
20
|
+
AND metadata_value = '1';
|
|
21
|
+
|
|
22
|
+
INSERT INTO taproot_assertions(assertion_key)
|
|
23
|
+
SELECT NULL WHERE NOT EXISTS (
|
|
24
|
+
SELECT 1 FROM taproot_metadata
|
|
25
|
+
WHERE metadata_key = 'canonical_json_version'
|
|
26
|
+
AND metadata_value = '2'
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
INSERT INTO taproot_migrations(version, name)
|
|
30
|
+
VALUES (3, 'canonical-statement-text')
|
|
31
|
+
ON CONFLICT(version) DO NOTHING;
|