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: 3 additions & 2 deletions docs/ADR-GKS-GENESISRAG17.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
version: "0.5.2b"
version: "0.5.3b"
created_at: "2026-09-07T23:30:00+07:00,RWANG,working-tree"
last_update: "2026-09-27T10:00:00+07:00,Claude Opus 5.5,working-tree"
last_update: "2026-09-27T17:00:00+07:00,Claude Opus 5.5,working-tree"
status: "accepted"
approval_owner: "Boss (บอส)"
approval_recorded_at: "2026-09-07T23:00:00+07:00"
Expand Down Expand Up @@ -232,6 +232,7 @@ evidence.
| Version | Date | Status | Summary | Commit Hash | Agent |
|---|---|---|---|---|---|
| 0.5.2b | 2026-09-27 | accepted | Cross-reference: ADR-GKS-PIPELINE-VISIBILITY (accepted 2026-09-27) keeps submit-time canonical entities invisible to legacy reads until a mentioning run is PUBLISHED; pipeline tools and Stage 9 reuse are unchanged. | working-tree | Claude Opus 5.5 |
| 0.5.3b | 2026-09-27 | accepted | GKS-PIP-001 (owner decision): a submitted batch must list its nine stage identities in catalog order. Receipts are still matched to the stored decision order-insensitively, so decisions stored before this rule are not stranded. See GKS-PORT-CONTRACT 0.10.2b. | working-tree | Claude |
| 0.5.1b | 2026-09-27 | accepted | Added the optional `GKS_PIPELINE_WORKER_CREDENTIAL` split so source and worker roles are bound to distinct secrets; unset keeps the single-credential compatibility mode. | working-tree | Claude Opus 5.5 |
| 0.5.0b | 2026-09-11 | accepted | Implemented structured-record profile contract revision 2 on the GKS side (ADR-075 Phase 2, rollout step 2 of 3): `PIPELINE_ONTOLOGY_VERSION` is `ontology_v2`, `PIPELINE_SUPPORTED_ONTOLOGY_VERSIONS` is `{ontology_v1, ontology_v2}`, the Stage 11 `validEndpoint` ternary is replaced by the per-version predicate -> endpoint table `PIPELINE_ONTOLOGY_ENDPOINTS` (v2 adds `HAS_COMPONENT`, `PRICED_AT`, `IN_CATEGORY` and the `PACKAGE`/`CATEGORY`/`PRICE_TIER` endpoint types), and the Stage 17 knowledge dimension checks set membership and each fact against its own version instead of equality with `ontology_v1`. `rule_v1`, `parseStructuredClaim`, Stage 12 and the C-10 bitemporal count are unchanged. Must merge after the GenesisBlock worker change that accepts both versions. | working-tree | Claude Opus 5 |
| 0.4.4b | 2026-09-11 | accepted | Fixed a residual of contract item C-10: the Stage 17 expected bitemporal lane count now includes HELD rows that carry valid time, not only facts, using the same row classification the worker applies (`temporalRows()` covers facts and held together; a row is dated unless validFrom/validTo are undefined/null/`not_applicable` and status is undefined/`not_applicable`). A decision with a dated held row previously disagreed with the worker's count even though the 0.4.3b fix already matched on facts alone. The all-`not_applicable` path (expect 0, lane `not_applicable`) now also considers facts and held together. Held rows already make the knowledge dimension WARN; this only corrects the graph-dimension reason, never whether anything publishes. | working-tree | Claude Opus 5 |
Expand Down
15 changes: 12 additions & 3 deletions docs/GKS-PORT-CONTRACT.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
version: "0.10.1b"
version: "0.10.2b"
created_at: "2026-08-12T10:05:34+07:00,ATHER,working-tree"
last_update: "2026-09-27T10:00:00+07:00,Claude"
last_update: "2026-09-27T17:00:00+07:00,Claude"
status: "beta"
approval_owner: "Boss (บอส)"
approval_recorded_at: "2026-08-12T10:16:19+07:00"
Expand Down Expand Up @@ -190,7 +190,15 @@ idempotent replies. A same identity with a different hash is a conflict.
### Stage identity and receipt ordering

The nine stage identities in a batch are fixed to stages 9 through 17 and their
`DPS-KI-*` ids. They are carried into receipts and evidence unchanged. The
`DPS-KI-*` ids. A submitted batch lists them **in catalog order**, 9 through 17.
Any other order is `gks_invalid_request`, and the message names the first
position that is wrong, for example `stages[1] must be stage 10: stages are
listed in catalog order, 9 through 17.`

The identities are carried into receipts and evidence unchanged. A receipt is
matched to its stored decision regardless of the order it lists them in. A
decision stored before the order rule keeps the order it was submitted in, and
the worker echoes that order, so rejecting it would strand the decision. The
physical order is:

```text
Expand Down Expand Up @@ -542,6 +550,7 @@ implementation package name appears in the client.
| Version | Date | Status | Summary | Commit Hash | Agent |
|---|---|---|---|---|---|
| 0.10.1b | 2026-09-27 | beta | Per ADR-GKS-PIPELINE-VISIBILITY (accepted 2026-09-27): legacy reads, the legacy resolution pool and D9 operands exclude unpublished GenesisRAG17 entities; `lookupResolutionCandidates` gains `includeUnpublishedPipeline` for Stage 9 reuse; D9 MERGE refuses to supersede a pipeline-origin entity (`gks_conflict`). No tool request or result shape changes. | working-tree | Claude |
| 0.10.2b | 2026-09-27 | beta | GKS-PIP-001 (owner decision): `gks_pipeline_submit` requires stage identities in catalog order 9 through 17. Receipts stay order-insensitive against the stored decision. | working-tree | Claude |
| 0.10.0b | 2026-09-24 | beta | Implements optional per-client hash-backed direct HTTP read grants while preserving the MSP profile; grants are not enabled by default and production rollout remains separate. | working-tree | RWANG |
| 0.9.0b | 2026-09-24 | beta | Adds the approved direct-client read-only grant profile while preserving MSP auth for governed writes; concrete identity verification and activation remain unimplemented. | working-tree | RWANG |
| 0.8.1b | 2026-09-22 | beta | Removed the stale port-version-1 statement that contradicted the selected GKS-owned SQLite production profile; deployment evidence remains separately gated. | working-tree | RWANG |
Expand Down
7 changes: 4 additions & 3 deletions docs/reports/2026-09-27-p1-c0-closure.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
version: "0.1.2b"
version: "0.1.3b"
created_at: "2026-09-27T10:30:00+07:00,Claude,working-tree"
last_update: "2026-09-27T16:00:00+07:00,Claude"
last_update: "2026-09-27T17:00:00+07:00,Claude"
status: "candidate"
superseded_by: null
attributes:
Expand Down Expand Up @@ -138,7 +138,7 @@ versioned rollout, not with this closure.
| Requirement | Current behaviour | Proposed change |
|---|---|---|
| GKS-API-002 | ~~`jsonrpc: "2.0"` is not validated~~ | **Done:** any other version is a -32600 Invalid Request ([ADR-GKS-C0-QUALIFICATION](../ADR-GKS-C0-QUALIFICATION.md) D3, 0.3.1) |
| GKS-PIP-001 | stage identities are accepted in any order | enforce catalog order |
| GKS-PIP-001 | ~~stage identities are accepted in any order~~ | **Done:** a submitted batch must list them in catalog order; receipts stay order-insensitive ([GKS-PORT-CONTRACT](../GKS-PORT-CONTRACT.md) 0.10.2b) |
| GKS-ING-004 | hashes the decoded string, so a lone surrogate becomes U+FFFD | hash raw bytes, or reject lone surrogates |
| GKS-ONT-002 | legacy promote relations have no endpoint type check | apply the ontology endpoint table |
| GKS-PIP-008 | a legacy export with an out-of-range cursor returns empty | return `gks_invalid_request`, as the pipeline export does |
Expand All @@ -153,3 +153,4 @@ versioned rollout, not with this closure.
| 0.1.0b | 2026-09-27 | candidate | P1 C0 closure: executable baseline lock and CI gate, lost-response replay PASS, IMMEDIATE write transactions and error-code discipline, C0 acceptance evidence | working-tree | Claude |
| 0.1.1b | 2026-09-27 | candidate | GKS-MIG-002 closed by the C0.4 golden corpus v2 | working-tree | Claude |
| 0.1.2b | 2026-09-27 | candidate | GKS-API-002 closed: JSON-RPC version is validated | working-tree | Claude |
| 0.1.3b | 2026-09-27 | candidate | GKS-PIP-001 closed: submitted stage identities must be in catalog order | working-tree | Claude |
14 changes: 11 additions & 3 deletions packages/gks-contracts/src/pipeline.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,12 @@ export function authorizePipelineRequest(input, { relayCredential, role, scope }
return Object.freeze({ principalId: principal.principalId, role: principal.role, scope: principal.scope });
}

export function validateStageIdentities(input, runId, label = "stages") {
// GKS-PIP-001: a submitted batch lists its stage identities in catalog order,
// 9 through 17. Receipts pass `catalogOrder: false`: they echo a stored
// decision, which is matched order-insensitively (persistence
// sameStageIdentity), and a decision stored before this rule keeps the order
// it was submitted in. Requiring the order there would strand it.
export function validateStageIdentities(input, runId, label = "stages", { catalogOrder = true } = {}) {
if (!Array.isArray(input) || input.length !== PIPELINE_STAGE_CATALOG.length) invalid(`${label} must contain exactly the nine stage identities.`);
const seen = new Set();
const normalized = input.map((stage, index) => {
Expand All @@ -262,6 +267,9 @@ export function validateStageIdentities(input, runId, label = "stages") {
const expected = PIPELINE_STAGE_BY_NUMBER[stageNumber];
if (stage.pipelineStageId !== expected.pipelineStageId) invalid(`${label}[${index}].pipelineStageId does not match stage ${stageNumber}.`);
if (seen.has(stageNumber)) invalid(`${label} contains duplicate stage ${stageNumber}.`);
if (catalogOrder && stageNumber !== PIPELINE_STAGE_CATALOG[index].stageNumber) {
invalid(`${label}[${index}] must be stage ${PIPELINE_STAGE_CATALOG[index].stageNumber}: stages are listed in catalog order, 9 through 17.`);
}
seen.add(stageNumber);
for (const key of ["runId", "executionStepId", "attemptId"]) requirePipelineString(stage[key], `${label}[${index}].${key}`);
if (stage.runId !== runId) invalid(`${label}[${index}].runId must match runId.`);
Expand Down Expand Up @@ -455,7 +463,7 @@ export function validatePipelineReceipt(input) {
const runId = requirePipelineString(input.runId, "receipt.runId");
const decisionId = requirePipelineString(input.decisionId, "receipt.decisionId");
const decisionHash = validateHash(input.decisionHash, "receipt.decisionHash");
const stages = validateStageIdentities(input.stages, runId, "receipt.stages");
const stages = validateStageIdentities(input.stages, runId, "receipt.stages", { catalogOrder: false });
const graphReceiptHash = validateHash(input.graphReceiptHash, "receipt.graphReceiptHash");
const derivedHash = validateHash(input.derivedHash, "receipt.derivedHash");
if (!isPlainObject(input.executionTimes)) invalid("receipt.executionTimes is required.");
Expand Down Expand Up @@ -506,7 +514,7 @@ export function validatePipelineGraphReceipt(input) {
const runId = requirePipelineString(input.runId, "graphReceipt.runId");
const decisionId = requirePipelineString(input.decisionId, "graphReceipt.decisionId");
const decisionHash = validateHash(input.decisionHash, "graphReceipt.decisionHash");
const stages = validateStageIdentities(input.stages, runId, "graphReceipt.stages");
const stages = validateStageIdentities(input.stages, runId, "graphReceipt.stages", { catalogOrder: false });
if (!isPlainObject(input.transaction)) invalid("graphReceipt.transaction is required.");
for (const field of ["id", "frontier", "checkpoint"]) requirePipelineString(input.transaction[field], `graphReceipt.transaction.${field}`);
if (!isPlainObject(input.readback) || typeof input.readback.ok !== "boolean") invalid("graphReceipt.readback is invalid.");
Expand Down
4 changes: 4 additions & 0 deletions scripts/c0-corpus/build-cases.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,10 @@ const TOOL_STEPS = {
const batch = batchFor("c0-submit");
await pipelineSteps(s, batch, "submit");
s.call("replay", "gks_pipeline_submit", { schemaVersion: PIPELINE_SCHEMA_VERSION, scope: batch.scope, batch, ...source }, { ok: true, match: { idempotent: true } });
// GKS-PIP-001: stage identities are listed in catalog order, 9 through 17.
const unordered = batchFor("c0-submit-unordered");
const stages = [unordered.stages[0], unordered.stages[2], unordered.stages[1], ...unordered.stages.slice(3)];
s.call("outOfOrder", "gks_pipeline_submit", { schemaVersion: PIPELINE_SCHEMA_VERSION, scope: unordered.scope, batch: { ...unordered, stages }, ...source }, { toolError: "gks_invalid_request", toolMessage: "stages[1] must be stage 10: stages are listed in catalog order, 9 through 17." });
},
async gks_pipeline_claim(s) { await pipelineSteps(s, batchFor("c0-claim"), "claim"); },
async gks_pipeline_graph_receipt(s) {
Expand Down
24 changes: 24 additions & 0 deletions tests/contract/c0-acceptance.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -147,12 +147,36 @@ describe("C0 identity and pipeline acceptance", () => {
["a duplicated stage", (stages) => [...stages.slice(0, 8), stages[0]]],
["a missing stage", (stages) => stages.slice(0, 8)],
["a stage under the wrong DPS-KI id", (stages) => stages.map((stage, index) => (index === 0 ? { ...stage, pipelineStageId: stages[1].pipelineStageId } : stage))],
["stages out of catalog order", (stages) => [stages[0], stages[2], stages[1], ...stages.slice(3)]],
["stages in reverse order", (stages) => [...stages].reverse()],
])("rejects %s", async (_label, mutate) => {
const { service } = harness();
const batch = makeBatch({ id: "batch-stage-identities" });
await expect(service.pipelineSubmit(submitArgs({ ...batch, stages: mutate(batch.stages) }))).rejects.toMatchObject({ code: "gks_invalid_request" });
});

it("names the first stage that breaks catalog order", async () => {
const { service } = harness();
const batch = makeBatch({ id: "batch-stage-order" });
const swapped = [batch.stages[0], batch.stages[2], batch.stages[1], ...batch.stages.slice(3)];
await expect(service.pipelineSubmit(submitArgs({ ...batch, stages: swapped }))).rejects.toMatchObject({
code: "gks_invalid_request",
message: "stages[1] must be stage 10: stages are listed in catalog order, 9 through 17.",
});
});

// Receipts are matched to the stored decision without regard to order: a
// decision stored before the order rule keeps its submitted order, and the
// worker echoes whatever the decision carries.
it("still accepts a graph receipt whose stage identities are listed out of order", async () => {
const { service } = harness();
const batch = makeBatch({ id: "batch-receipt-stage-order" });
const { decision } = await submitAndClaim(service, batch);
const receipt = graphReceiptFor(decision);
const result = await service.pipelineGraphReceipt({ receipt: { ...receipt, stages: [...receipt.stages].reverse() }, ...auth(batch.scope, "worker") });
expect(result).toMatchObject({ accepted: true, idempotent: false });
});

// GKS-PIP-003: claim is non-destructive -- two workers can see one decision,
// and the second worker's identical receipt is an idempotent replay while a
// different one is a conflict.
Expand Down
Loading
Loading