Skip to content
Merged
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
5 changes: 4 additions & 1 deletion docs/deck/contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ with a current revision and an immutable revision subject. `requestPublication`
constructs those facts; it does not publish them. A real curator job must use
normal governed conditional writes: advance the current revision atomically,
expect the old revision, and expect the new manifest slot to be absent. This
source does not deploy that job or grant it a credential.
source does not deploy that job or grant it a credential. The separate
[conditional curator source](curator.md) implements plan construction and
durable publication custody with explicit adapters; production adapters,
credential restrictions and served admission remain separate gates.

The admitted reader supplies the exact graph, request ID, current revision,
revision subject and complete manifest. Unknown fields, duplicate JSON keys,
Expand Down
37 changes: 37 additions & 0 deletions docs/deck/curator-demo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Curator publication custody demo

Trigger: a first publication changes a synthetic fixture, but its reply is
lost. Watch UNKNOWN become CONFIRMED after a new job instance reads the same
durable custody. The send count stays one; a changed payload in that revision
refuses. The adapters in this recording are synthetic, with no native server,
credential or production request. The separate native qualifier exercises the
generated HTTP update and real native commit/recovery.

Base `b290c515bfb3cda35eba38af3533c6c9200a0003` has the request fact builder but
no publication job. The demo checks that exact tree with a positive module
control. Demonstrated source is
`5727afd66297160c64006558fe82433247c2bac9`; subsequent recording/result files
do not change its implementation. This demonstrates the new library, rather
than an installed effect or a before/after server benchmark.

Reproduce from a checkout containing that base object:

```sh
node scripts/demo-deck-curator.mjs
```

The small raw [output recording](curator-demo.output) also serves as the readable
transcript. Download it together with its [timing file](curator-demo.timing).
Playback was verified with the installed util-linux `scriptreplay`; the
recording retains real output and timing. This command deliberately slows its
brief output for reading:

```sh
scriptreplay --log-out curator-demo.output --log-timing curator-demo.timing --divisor 0.05
```

This is a terminal recording, with no inline video-player claim. Both small
files are retained in this repository's existing Deck evidence directory.
Neither the recording nor the synthetic native controls establish production
least-privilege credential enforcement, graph policy, served read cost or
human acceptance.
14 changes: 14 additions & 0 deletions docs/deck/curator-demo.output
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Script started on 2026-10-25 18:22:44-04:00 [COMMAND="node scripts/demo-deck-curator.mjs" <not executed on terminal>]
Base b290c515bfb3cda35eba38af3533c6c9200a0003 : request fact builder exists; publication job absent.
Demonstrated source HEAD: 5727afd66297160c64006558fe82433247c2bac9
Target: source library, synthetic transport and reader; no service or credential.
Trigger: first publication; transport loses its reply after the fixture changes.
Before send, durable custody: UNKNOWN
First call: {"state":"UNKNOWN","sent":true,"transaction":null}
Restart with the SAME durable directory; reconcile by reading.
Recovery: {"state":"CONFIRMED","sent":false,"transaction":null}
Send count: 1
Changed payload in same revision refused: true
Observed: UNKNOWN -> CONFIRMED by read-back; one send retained.

Script done on 2026-10-25 18:22:44-04:00 [COMMAND_EXIT_CODE="0"]
11 changes: 11 additions & 0 deletions docs/deck/curator-demo.timing
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
0.106361 102
0.002925 68
0.000018 83
0.000022 82
0.013912 39
0.000146 64
0.000115 64
0.001911 65
0.000533 15
0.002559 48
0.000092 65
95 changes: 95 additions & 0 deletions docs/deck/curator-native-results.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
{
"candidate": {
"features": {
"dev-tools": false,
"full": true,
"jemalloc": true,
"lancedb": false,
"mcp": true,
"onnx": true,
"owl": true,
"oxicompare": false,
"reactive-reasoner": true,
"remote": true,
"server": true,
"shacl": true
},
"git_dirty": false,
"git_sha": "703e2ce83fd42992ded8214ff62320b84204b0d4",
"read_contracts": [
"quipu-acquisition/v1",
"quipu-acquisition/composed-v1",
"quipu-eligibility/v1"
],
"version": "0.16.3"
},
"candidate_sha256": "bdba9adb323696e0b32e10cc36f6cd6a7af3f14462fcd4c4e869f85ffd0ce452",
"synthetic_loopback_only": true,
"real_credential_used": false,
"production_authority_proven": false,
"results": [
{
"case": "identical-first-replay",
"no_partial_request_mutation": true
},
{
"case": "initial-and-next",
"exact_old_manifest_retained": true,
"escaped_text_is_data": true
},
{
"case": "stale-revision",
"no_partial_request_mutation": true
},
{
"case": "changed-item",
"no_partial_request_mutation": true
},
{
"case": "foreign-item",
"no_partial_request_mutation": true
},
{
"case": "ambiguous-item-revision",
"no_partial_request_mutation": true
},
{
"case": "ambiguous-item-status",
"no_partial_request_mutation": true
},
{
"case": "closed-item",
"no_partial_request_mutation": true
},
{
"case": "ambiguous-current-head",
"no_partial_request_mutation": true
},
{
"case": "occupied-immutable-revision",
"no_partial_request_mutation": true
},
{
"case": "concurrent-different-payload",
"exactly_one_atomic_publication": true
},
{
"case": "native-commit-lost-response-restart",
"read_only_recovery": true,
"sends": 1
},
{
"case": "graph-mutation-boundary",
"positive_item_control_unchanged": true,
"answer_graph_unchanged": true
}
],
"qualified_curator_source": "5727afd66297160c64006558fe82433247c2bac9",
"source_sha256": {
"web/deck/curator.mjs": "d6b3fa6a4bfdb7e25b4ec5169150c18714a4ffbf82471d6e4d9f41e9a52c6f8a",
"scripts/deck-curator-job.mjs": "9825094b63e6a403e2a8fc144a2da24fb8dba207ddedbe17cde09a26392a5461",
"scripts/qualify-deck-curator.mjs": "cbc3520daeba6a905161178c06eea8e31ba604e02ecfa02cd08176adb6c17d04"
},
"home_guard": "command exit0;157 paths and5 UID resources CLEAN",
"owned_server_reaped_and_fixture_removed": true
}
80 changes: 80 additions & 0 deletions docs/deck/curator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Conditional curator source

`publicationPlan` builds one existing SPARQL `/update` operation. A first
publication requires no current revision and revision 1. A later publication
requires the exact previous head and manifest and advances by exactly one.
The new immutable revision subject must have no facts. Both forms check the
canonical item's exact revision and open status, rejecting additional values
in either slot. The only DELETE/INSERT target is the request graph. Previous
manifests, answers and item facts are retained.

`scripts/deck-curator-job.mjs` supplies `CuratorJob` with explicit injected
inspection, send and read-back adapters. It selects no endpoint, credential,
timer or production store. Before proceeding it checks the configured native
store lineage and retained graph-creation identity against inspection of a
registered committed graph. This separate inspection is not an atomic graph
replacement guard or proof of server-side credential restrictions.

The job requires an existing owned directory with mode 0700 and no symlinked
path. It creates a mode-0600 custody file exclusively, containing the complete
plan, store lineage, graph pin and their digest. The file and directory are
synced before the one send. The custody slot is keyed by store, request graph,
request ID and revision; a different payload or graph pin conflicts. Partial,
corrupt, foreign-owned or symlinked records refuse. The directory must remain
on the job's durable volume across restarts; using a fresh directory defeats
that local custody guarantee.

Every retained file stays UNKNOWN, including after successful confirmation.
Subsequent instances may only read it and reconcile; they never resend. A
lost response or read timeout therefore retains the exact publication intent.
`reconcile` can read an expired request's retained intent, while `publish`
refuses to initiate an expired publication. No automatic cleanup or retry is
provided. This is local job custody, not a cross-host exactly-once protocol.
Concurrent distinct publications are additionally controlled by the native
conditional update.

A response must describe the exact request graph, one positive transaction,
two assertions and zero or one retraction. A zero-change report cannot be
mistaken for a changed transaction. CONFIRMED also requires complete exact
manifest/current-revision read-back from the pinned store. Read-only recovery
may confirm matching state without a transaction ID; it does not attribute
that state to this job or mint a signed receipt. Missing, foreign or changed
evidence remains UNKNOWN. No accepted decision or answered disposition is
created by publishing a request.

Production adapters are still owed. They must pin the actual server/store,
native graph registration, bounded admitted reader definitions and cost, and
use the sanctioned authenticated write path. A dedicated named credential
must be restricted by the server's admitted policy to the request graph.
The generated query's narrow mutation target is not a security boundary for
a broad bearer. Declared actor/source fields are attribution, not verified
authentication. This source grants no credential, graph creation, protection,
registration, served enablement or enrollment authority. Production profile
union and external-writer/graph-replacement assumptions need admission too.

## Repeatable qualification

`just ci` covers query construction, strict transaction/read-back validation,
durable-before-send ordering, changed-payload refusal, restart/lost-response
reconciliation, concurrent job instances and unsafe custody. Negative target
controls reject missing/replaced/overlay graphs and wrong store lineage.

The explicit qualifier starts one loopback native server with a new disposable
store, private HOME/configuration and a synthetic test bearer. It opens no
installed store and inherits no production credential or provider setting.
Its positive item facts control the graph-mutation checks. It runs the actual
generated update through HTTP, checks native reports and retained manifests,
races two distinct publications, and loses a reply after a real native commit
before read-only recovery by a new job instance. It reaps its child and removes
the fixture in `finally`, with a 90-second server bound.

```sh
QUIPU_SERVER=/absolute/path/to/pinned/quipu-server \
QUIPU_SERVER_SHA256=<exact-sha256> node scripts/qualify-deck-curator.mjs
```

[curator-native-results.json](curator-native-results.json) records the clean
release/full candidate and synthetic controls. That candidate is an explicit
source qualification artifact, not a claim about an installed or officially
released server. The fixture's broad synthetic bearer does not demonstrate
least-privilege credential enforcement, real enrollment or production cost.
69 changes: 69 additions & 0 deletions scripts/deck-curator-job.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Source-only job, with explicit injected governed transport and reader.
// No endpoint, credential, timer or production adapter is selected here.
import {open, lstat, realpath} from 'node:fs/promises';
import {constants} from 'node:fs';
import {resolve, join} from 'node:path';
import {createHash} from 'node:crypto';
import {publicationPlan, publicationReport, publicationReadback} from '../web/deck/curator.mjs';
import {Unknown, iri, text, exact, integer} from '../web/deck/value.mjs';

const hash = value => createHash('sha256').update(value).digest('hex');
export class CuratorJob {
constructor({directory, storeId, registration, inspect, send, readBack}) {
if (typeof directory !== 'string' || typeof inspect !== 'function' || typeof send !== 'function' || typeof readBack !== 'function') throw new Unknown('Explicit custody and governed adapters required');
exact(registration, ['iri', 'creation']); iri(registration.iri);
exact(registration.creation, ['actor', 'timestamp']); text(registration.creation.timestamp);
if (registration.creation.actor !== null) text(registration.creation.actor);
this.registration = structuredClone(registration); this.inspect = inspect;
this.directory = resolve(directory); this.storeId = iri(storeId); this.send = send; this.readBack = readBack;
}
async publish(input) {return this.run(input, false);}
async reconcile(input) {return this.run(input, true);}
async run(input, readOnly) {
const plan = publicationPlan(readOnly ? {...input, now: 0} : input), payload = JSON.stringify({storeId: this.storeId, registration: this.registration, plan});
if (this.registration.iri !== plan.boundary.requestGraph) throw new Unknown('Foreign configured graph');
const admitted = await this.inspect(this.registration.iri);
const graph = admitted.graph; integer(graph?.g);
if (admitted.storeId !== this.storeId || graph.iri !== this.registration.iri || graph.class !== 'committed' || JSON.stringify(graph.creation) !== JSON.stringify(this.registration.creation)) throw new Unknown('Unregistered or replaced publication target');
const info = await lstat(this.directory);
if (!info.isDirectory() || info.uid !== process.getuid() || (info.mode & 0o777) !== 0o700 || await realpath(this.directory) !== this.directory) throw new Unknown('Owned private custody directory required');
// Same request revision has ONE custody slot, including changed payloads.
const key = hash(JSON.stringify([this.storeId, plan.boundary.requestGraph, plan.next.id, plan.next.revision]));
const path = join(this.directory, key + '.json');
const record = {state: 'UNKNOWN', digest: hash(payload), storeId: this.storeId, registration: this.registration, plan};
const bytes = JSON.stringify(record) + '\n';
if (Buffer.byteLength(bytes) > 131072) throw new Unknown('Publication custody too large');
let fresh = false, handle;
try {
if (readOnly) {const error = new Error('Reconciliation requires retained custody'); error.code = 'EEXIST'; throw error;}
handle = await open(path, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
await handle.writeFile(bytes); await handle.sync();
await handle.close(); handle = null;
const dir = await open(this.directory, constants.O_RDONLY | constants.O_DIRECTORY);
try {await dir.sync();} finally {await dir.close();}
fresh = true;
} catch (error) {
if (handle) await handle.close();
if (error.code !== 'EEXIST') throw error;
const retained = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
let existing;
try {
const stat = await retained.stat();
if (!stat.isFile() || stat.uid !== process.getuid() || (stat.mode & 0o777) !== 0o600 || stat.size > 131072) throw new Unknown('Invalid retained custody');
existing = JSON.parse(await retained.readFile('utf8'));
} finally {await retained.close();}
if (existing.state !== 'UNKNOWN' || existing.digest !== record.digest || JSON.stringify({storeId: existing.storeId, registration: existing.registration, plan: existing.plan}) !== payload) throw new Unknown('Changed or incomplete publication custody');
}
let report = null;
if (fresh) {
try {report = await this.send(plan.query); publicationReport(report, plan);} catch {return {state: 'UNKNOWN', sent: true, transaction: null};}
}
// Retained UNKNOWN never resends. Read-only reconciliation is repeatable.
try {
const observed = await this.readBack(plan.next.id);
if (observed.storeId !== this.storeId) throw new Unknown('Foreign store readback');
publicationReadback(observed.requestRow, plan);
return {state: 'CONFIRMED', sent: fresh, transaction: report?.tx ?? null};
} catch {return {state: 'UNKNOWN', sent: fresh, transaction: null};}
}
}
41 changes: 41 additions & 0 deletions scripts/demo-deck-curator.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Focused library demo with synthetic adapters, not a native or served proof.
import assert from 'node:assert/strict';
import {mkdtemp, chmod, rm, readdir, readFile} from 'node:fs/promises';
import {join} from 'node:path';
import {tmpdir} from 'node:os';
import {spawnSync, execFileSync} from 'node:child_process';
import {CuratorJob} from './deck-curator-job.mjs';
import {requestPublication, revisionIri} from '../web/deck/model.mjs';
import {ask, boundary} from '../tests/deck-fixture.mjs';

const directory = await mkdtemp(join(tmpdir(), 'rikuy-curator-demo-')); await chmod(directory, 0o700);
const next = {...ask, revision: 1}, input = {boundary, next, previous: null, now: 1};
const storeId = 'urn:uuid:synthetic-demo-store';
const creation = {actor: 'synthetic-curator', timestamp: '2026-10-25T00:00:00Z'};
let sends = 0, present = false;
const options = {directory, storeId, registration: {iri: boundary.requestGraph, creation}, inspect: async () => ({storeId, graph: {iri: boundary.requestGraph, g: 1, class: 'committed', creation}}), send: async () => {
const file = (await readdir(directory))[0];
const saved = JSON.parse(await readFile(join(directory, file), 'utf8'));
console.log('Before send, durable custody:', saved.state); assert.equal(saved.state, 'UNKNOWN');
sends++; present = true; throw Error('Synthetic lost reply after fixture mutation');
}, readBack: async () => {
assert(present); return {storeId, requestRow: {graph: boundary.requestGraph, requestId: next.id, currentRevision: 1, revisionIri: revisionIri(next), manifest: requestPublication(next, boundary).datums[1].o.value, complete: true}};
}};
try {
const base = 'b290c515bfb3cda35eba38af3533c6c9200a0003';
assert.equal(spawnSync('git', ['cat-file', '-e', base + ':web/deck/model.mjs'], {stdio: 'ignore'}).status, 0);
const before = spawnSync('git', ['cat-file', '-e', base + ':scripts/deck-curator-job.mjs'], {stdio: 'ignore'}).status;
assert.equal(before, 128);
console.log('Base', base, ': request fact builder exists; publication job absent.');
console.log('Demonstrated source HEAD:', execFileSync('git', ['rev-parse', 'HEAD'], {encoding: 'utf8'}).trim());
console.log('Target: source library, synthetic transport and reader; no service or credential.');
console.log('Trigger: first publication; transport loses its reply after the fixture changes.');
const lost = await new CuratorJob(options).publish(input); console.log('First call:', JSON.stringify(lost)); assert.equal(lost.state, 'UNKNOWN');
console.log('Restart with the SAME durable directory; reconcile by reading.');
const recovered = await new CuratorJob(options).reconcile(input); console.log('Recovery:', JSON.stringify(recovered)); assert.equal(recovered.state, 'CONFIRMED'); assert.equal(recovered.sent, false);
console.log('Send count:', sends); assert.equal(sends, 1);
let refused = false;
try {await new CuratorJob(options).publish({...input, next: {...next, title: 'Changed payload'}});} catch {refused = true;}
console.log('Changed payload in same revision refused:', refused); assert(refused); assert.equal(sends, 1);
console.log('Observed: UNKNOWN -> CONFIRMED by read-back; one send retained.');
} finally {await rm(directory, {recursive: true, force: true});}
Loading
Loading