feat: replace revision counters with domain state contracts
Use content digests, key fingerprints, binding and trust identities, immutable mutation receipts, and observation tokens instead of state-update ordinals across API, storage, Runtime, and Web contracts. Preserve historical replay and secret material through frozen migrations; preflight uncertain removals before cutover, resume provisioning receipts, validate encrypted migration candidates, and align fresh/upgraded schemas. Include reviewed regression fixes, generated artifacts, and validation evidence.
This commit is contained in:
@@ -5,7 +5,7 @@ declare const Deno: {
|
||||
import {
|
||||
createSubjektivSubject,
|
||||
parseMemoryStagingListResponse,
|
||||
parseSubjektivMemoryListRevisionsResponse,
|
||||
parseSubjektivMemoryListChangesResponse,
|
||||
parseSubjektivMemoryQueryResponse,
|
||||
parseSubjektivMemoryReadResponse,
|
||||
parseSubjektivResidentSurfaceResponse,
|
||||
@@ -75,9 +75,8 @@ function subject(id = "subject-1") {
|
||||
id,
|
||||
role: "Release coordinator",
|
||||
behavior_md: "Prefer explicit evidence.",
|
||||
behavior_revision: 2,
|
||||
state: "active",
|
||||
store_revision: 12,
|
||||
memory_fingerprint: "fingerprint-12",
|
||||
created_at: "2026-09-01T00:00:00Z",
|
||||
updated_at: "2026-09-02T00:00:00Z",
|
||||
};
|
||||
@@ -110,7 +109,7 @@ function currentWorker() {
|
||||
function queryItem(id = "memory-1") {
|
||||
return {
|
||||
id,
|
||||
revision: 3,
|
||||
change_id: "change-current",
|
||||
kind: "decision",
|
||||
state: "active",
|
||||
claim: "Keep provenance typed.",
|
||||
@@ -127,12 +126,11 @@ function detailFixture() {
|
||||
worker_id: "worker-1",
|
||||
flow_selector: "builtin:coder-review",
|
||||
flow_definition_id: "flow-1",
|
||||
flow_definition_revision: 7,
|
||||
};
|
||||
return {
|
||||
memory_id: "memory-1",
|
||||
revision: 2,
|
||||
current_revision: 3,
|
||||
change_id: "change-earlier",
|
||||
current_change_id: "change-current",
|
||||
kind: "decision",
|
||||
state: "resolved",
|
||||
claim: "Keep provenance typed.",
|
||||
@@ -173,13 +171,13 @@ function detailFixture() {
|
||||
source_refs_total: 2,
|
||||
source_refs_truncated: true,
|
||||
}],
|
||||
derived_from: [{ memory_id: "memory-parent", revision: 4 }],
|
||||
derived_from: [{ memory_id: "memory-parent", change_id: "change-parent" }],
|
||||
evidence_next_cursor: "evidence-page-2",
|
||||
evidence_has_more: true,
|
||||
};
|
||||
}
|
||||
|
||||
Deno.test("Subject parsers enforce identity, exact enums, safe revisions, and bounds", () => {
|
||||
Deno.test("Subject parsers enforce identity, exact enums, bounded fingerprints, and bounds", () => {
|
||||
assertEquals(
|
||||
parseSubjektivSubjectResponse(subject(), "subject-1"),
|
||||
subject(),
|
||||
@@ -196,9 +194,9 @@ Deno.test("Subject parsers enforce identity, exact enums, safe revisions, and bo
|
||||
() =>
|
||||
parseSubjektivSubjectResponse({
|
||||
...subject(),
|
||||
store_revision: Number.MAX_SAFE_INTEGER + 1,
|
||||
memory_fingerprint: 12,
|
||||
}),
|
||||
"safe integer",
|
||||
"bounded identifier",
|
||||
);
|
||||
assertThrows(
|
||||
() => parseSubjektivSubjectResponse({ ...subject(), id: "x".repeat(513) }),
|
||||
@@ -313,7 +311,7 @@ Deno.test("Subject behavior update uses CAS and preserves exact text", async ()
|
||||
return Response.json({
|
||||
...subject("subject/one"),
|
||||
behavior_md: "Line one.\nLine two.",
|
||||
behavior_revision: 3,
|
||||
|
||||
});
|
||||
}) as typeof fetch;
|
||||
|
||||
@@ -321,9 +319,8 @@ Deno.test("Subject behavior update uses CAS and preserves exact text", async ()
|
||||
fetchFn,
|
||||
"workspace one",
|
||||
"subject/one",
|
||||
{ expected_behavior_revision: 2, behavior_md: "Line one.\nLine two." },
|
||||
{ expected_behavior_md: "Prefer explicit evidence.", behavior_md: "Line one.\nLine two." },
|
||||
);
|
||||
assertEquals(updated.behavior_revision, 3);
|
||||
assertEquals(updated.behavior_md, "Line one.\nLine two.");
|
||||
assertEquals(
|
||||
requests[0].path,
|
||||
@@ -332,20 +329,20 @@ Deno.test("Subject behavior update uses CAS and preserves exact text", async ()
|
||||
assertEquals(requests[0].init?.method, "PATCH");
|
||||
assertEquals(
|
||||
requests[0].init?.body,
|
||||
'{"expected_behavior_revision":2,"behavior_md":"Line one.\\nLine two."}',
|
||||
'{"expected_behavior_md":"Prefer explicit evidence.","behavior_md":"Line one.\\nLine two."}',
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("Subject behavior update reports conflicts without claiming Worker application", async () => {
|
||||
const conflictFetch =
|
||||
(async () =>
|
||||
Response.json({ message: "revision conflict" }, {
|
||||
Response.json({ message: "behavior content conflict" }, {
|
||||
status: 409,
|
||||
})) as typeof fetch;
|
||||
await assertSubjectCreateRejects(
|
||||
() =>
|
||||
updateSubjektivSubjectBehavior(conflictFetch, "workspace", "subject", {
|
||||
expected_behavior_revision: 1,
|
||||
expected_behavior_md: "old",
|
||||
behavior_md: "new",
|
||||
}),
|
||||
"rejected",
|
||||
@@ -459,7 +456,7 @@ Deno.test("Resident surface parser accepts ready-empty and enforces snapshot inv
|
||||
snapshot_id: "snapshot-empty",
|
||||
body_md: "",
|
||||
memory_refs: [],
|
||||
built_from_store_revision: 0,
|
||||
built_from_memory_fingerprint: "fingerprint-0",
|
||||
created_at: "2026-09-02T00:00:00Z",
|
||||
},
|
||||
};
|
||||
@@ -506,7 +503,7 @@ Deno.test("Resident surface parser accepts ready-empty and enforces snapshot inv
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("Current Memory list parser rejects unknown variants, unsafe integers, cursors, and oversized pages", () => {
|
||||
Deno.test("Current Memory list parser rejects unknown variants, invalid change IDs, cursors, and oversized pages", () => {
|
||||
const response = {
|
||||
items: [queryItem()],
|
||||
has_more: true,
|
||||
@@ -533,9 +530,9 @@ Deno.test("Current Memory list parser rejects unknown variants, unsafe integers,
|
||||
() =>
|
||||
parseSubjektivMemoryQueryResponse({
|
||||
...response,
|
||||
items: [{ ...queryItem(), revision: 0 }],
|
||||
items: [{ ...queryItem(), change_id: "" }],
|
||||
}),
|
||||
"positive safe integer",
|
||||
"bounded identifier",
|
||||
);
|
||||
assertThrows(
|
||||
() =>
|
||||
@@ -572,7 +569,7 @@ Deno.test("Memory detail parser preserves body/evidence continuation, sources, o
|
||||
);
|
||||
assertEquals(parsed.derived_from, [{
|
||||
memory_id: "memory-parent",
|
||||
revision: 4,
|
||||
change_id: "change-parent",
|
||||
}]);
|
||||
});
|
||||
|
||||
@@ -586,9 +583,9 @@ Deno.test("Memory detail parser fails closed on identity, variants, bounds, and
|
||||
() =>
|
||||
parseSubjektivMemoryReadResponse({
|
||||
...fixture,
|
||||
current_revision: Number.MAX_SAFE_INTEGER + 1,
|
||||
current_change_id: 3,
|
||||
}),
|
||||
"safe integer",
|
||||
"bounded identifier",
|
||||
);
|
||||
const missingBodyContinuation = structuredClone(fixture) as Record<
|
||||
string,
|
||||
@@ -637,26 +634,26 @@ Deno.test("Memory detail parser fails closed on identity, variants, bounds, and
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("Memory revision parser preserves immutable states and checks response identity", () => {
|
||||
Deno.test("Memory change parser preserves immutable states and checks response identity", () => {
|
||||
const response = {
|
||||
memory_id: "memory-1",
|
||||
current_revision: 3,
|
||||
current_change_id: "change-current",
|
||||
items: [{
|
||||
revision: 3,
|
||||
change_id: "change-current",
|
||||
kind: "decision",
|
||||
state: "active",
|
||||
claim: "Current claim.",
|
||||
change_reason: "Clarified wording.",
|
||||
updated_at: "2026-09-03T00:00:00Z",
|
||||
}, {
|
||||
revision: 2,
|
||||
change_id: "change-earlier",
|
||||
kind: "decision",
|
||||
state: "resolved",
|
||||
claim: "Earlier claim.",
|
||||
change_reason: "Resolved.",
|
||||
updated_at: "2026-09-02T00:00:00Z",
|
||||
}, {
|
||||
revision: 1,
|
||||
change_id: "change-original",
|
||||
kind: "decision",
|
||||
state: "retracted",
|
||||
claim: "Original claim.",
|
||||
@@ -666,16 +663,16 @@ Deno.test("Memory revision parser preserves immutable states and checks response
|
||||
has_more: false,
|
||||
};
|
||||
assertEquals(
|
||||
parseSubjektivMemoryListRevisionsResponse(response, "memory-1"),
|
||||
parseSubjektivMemoryListChangesResponse(response, "memory-1"),
|
||||
response,
|
||||
);
|
||||
assertThrows(
|
||||
() => parseSubjektivMemoryListRevisionsResponse(response, "memory-2"),
|
||||
() => parseSubjektivMemoryListChangesResponse(response, "memory-2"),
|
||||
"identity does not match",
|
||||
);
|
||||
assertThrows(
|
||||
() =>
|
||||
parseSubjektivMemoryListRevisionsResponse({
|
||||
parseSubjektivMemoryListChangesResponse({
|
||||
...response,
|
||||
items: [{ ...response.items[0], state: "archived" }],
|
||||
}),
|
||||
@@ -683,14 +680,61 @@ Deno.test("Memory revision parser preserves immutable states and checks response
|
||||
);
|
||||
assertThrows(
|
||||
() =>
|
||||
parseSubjektivMemoryListRevisionsResponse({
|
||||
parseSubjektivMemoryListChangesResponse({
|
||||
...response,
|
||||
items: [{ ...response.items[0], revision: 4 }],
|
||||
items: [response.items[0], response.items[0]],
|
||||
}),
|
||||
"future revision",
|
||||
"Memory changes must be unique",
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("Memory changes preserve server order without interpreting opaque identities", () => {
|
||||
const item = { kind: "decision", state: "active", claim: "Claim", change_reason: "Reason", updated_at: "2026-09-01T00:00:00Z" };
|
||||
const response = {
|
||||
memory_id: "memory-1",
|
||||
current_change_id: "a/current",
|
||||
items: [{ ...item, change_id: "z/earlier" }, { ...item, change_id: "a/current" }],
|
||||
has_more: false,
|
||||
};
|
||||
assertEquals(parseSubjektivMemoryListChangesResponse(response), response);
|
||||
assertEquals(parseSubjektivMemoryReadResponse({ ...detailFixture(), change_id: "z/earlier", current_change_id: "a/current" }).change_id, "z/earlier");
|
||||
});
|
||||
|
||||
Deno.test("Memory identities reject numeric counters, empty and oversized strings, and legacy fields", () => {
|
||||
for (const value of [3, "", "x".repeat(513)]) {
|
||||
assertThrows(() => parseSubjektivMemoryReadResponse({ ...detailFixture(), change_id: value }), "bounded identifier");
|
||||
assertThrows(() => parseSubjektivMemoryReadResponse({ ...detailFixture(), current_change_id: value }), "bounded identifier");
|
||||
assertThrows(() => parseSubjektivSubjectResponse({ ...subject(), memory_fingerprint: value }), "bounded identifier");
|
||||
}
|
||||
assertThrows(() => parseSubjektivMemoryReadResponse({ ...detailFixture(), revision: 2 }), "unknown field");
|
||||
assertThrows(() => parseSubjektivSubjectResponse({ ...subject(), behavior_revision: 2 }), "unknown field");
|
||||
assertThrows(() => parseSubjektivSubjectResponse({ ...subject(), store_revision: 12 }), "unknown field");
|
||||
});
|
||||
|
||||
Deno.test("Behavior CAS sends the observed text byte-for-byte including empty content", async () => {
|
||||
for (const expected of ["", " Previous\nbehavior.\n"]) {
|
||||
let body: unknown;
|
||||
const fetchFn = (async (_path: string | URL | Request, init?: RequestInit) => {
|
||||
body = JSON.parse(String(init?.body));
|
||||
return Response.json({ ...subject(), behavior_md: "Replacement" });
|
||||
}) as typeof fetch;
|
||||
await updateSubjektivSubjectBehavior(fetchFn, "workspace", "subject-1", { expected_behavior_md: expected, behavior_md: "Replacement" });
|
||||
assertEquals(body, { expected_behavior_md: expected, behavior_md: "Replacement" });
|
||||
}
|
||||
});
|
||||
|
||||
Deno.test("Behavior saves with mismatched response text have unknown outcome and are not retried", async () => {
|
||||
let calls = 0;
|
||||
const fetchFn = (async () => {
|
||||
calls += 1;
|
||||
return Response.json(subject());
|
||||
}) as typeof fetch;
|
||||
await assertSubjectCreateRejects(() => updateSubjektivSubjectBehavior(fetchFn, "workspace", "subject-1", {
|
||||
expected_behavior_md: "Prefer explicit evidence.", behavior_md: "Replacement",
|
||||
}), "unknown_outcome", "could not be confirmed");
|
||||
assertEquals(calls, 1);
|
||||
});
|
||||
|
||||
Deno.test("Legacy staging parser remains strict for deprecated compatibility", () => {
|
||||
const origin = { kind: "worker_input", worker_id: "worker-1" };
|
||||
const response = {
|
||||
|
||||
@@ -44,8 +44,6 @@ const diagnostic = {
|
||||
function profileSettingsFixture(): Record<string, unknown> {
|
||||
return {
|
||||
workspace_id: "workspace 1",
|
||||
registry_revision: "config-source:7:tree:projection",
|
||||
config_revision: 7,
|
||||
tree_digest: "tree",
|
||||
projection_digest: "projection",
|
||||
default_profile: "workspace:coder",
|
||||
@@ -68,7 +66,6 @@ function profileSettingsFixture(): Record<string, unknown> {
|
||||
content_digest: "sha256:source",
|
||||
provenance: "project_profile_source_tree",
|
||||
editable: false,
|
||||
revision: "config-source:7",
|
||||
size_bytes: 128,
|
||||
diagnostics: [],
|
||||
}],
|
||||
@@ -86,7 +83,7 @@ Deno.test("profile settings requests use scoped API and strictly validate respon
|
||||
|
||||
try {
|
||||
const response = await fetchProfileSettings("workspace 1");
|
||||
assertEquals(response.config_revision, 7);
|
||||
assertEquals(response.tree_digest, "tree");
|
||||
assertEquals(response.sources[0].provenance, "project_profile_source_tree");
|
||||
assertEquals(requests.length, 1);
|
||||
assertEquals(requests[0].url, "/api/w/workspace%201/settings/profiles");
|
||||
@@ -103,7 +100,7 @@ Deno.test("workspace metadata requests use generated DTO shapes", async () => {
|
||||
workspace_id: "workspace 1",
|
||||
display_name: "Workspace",
|
||||
created_at: "2026-01-01T00:00:00Z",
|
||||
revision: "sha256:metadata",
|
||||
updated_at: "2026-01-02T00:00:00Z",
|
||||
source: "workspace-config",
|
||||
diagnostics: [diagnostic],
|
||||
};
|
||||
@@ -118,13 +115,13 @@ Deno.test("workspace metadata requests use generated DTO shapes", async () => {
|
||||
|
||||
try {
|
||||
assertEquals(
|
||||
(await fetchWorkspaceMetadata("workspace 1")).revision,
|
||||
"sha256:metadata",
|
||||
(await fetchWorkspaceMetadata("workspace 1")).updated_at,
|
||||
"2026-01-02T00:00:00Z",
|
||||
);
|
||||
assertEquals(
|
||||
(await updateWorkspaceMetadata("workspace 1", {
|
||||
display_name: "Renamed",
|
||||
revision: "sha256:metadata",
|
||||
expected_updated_at: "2026-01-02T00:00:00Z",
|
||||
})).workspace.workspace_id,
|
||||
"workspace 1",
|
||||
);
|
||||
@@ -135,7 +132,10 @@ Deno.test("workspace metadata requests use generated DTO shapes", async () => {
|
||||
assertEquals(requests[1].init?.method, "PUT");
|
||||
assertEquals(
|
||||
requests[1].init?.body,
|
||||
JSON.stringify({ display_name: "Renamed", revision: "sha256:metadata" }),
|
||||
JSON.stringify({
|
||||
display_name: "Renamed",
|
||||
expected_updated_at: "2026-01-02T00:00:00Z",
|
||||
}),
|
||||
);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
@@ -152,7 +152,6 @@ Deno.test("Workspace signing identity requests use flat settings routes", async
|
||||
workspace_id: "workspace 1",
|
||||
key_id: "workspace-signing-key",
|
||||
algorithm: "ed25519",
|
||||
revision: 1,
|
||||
state: "pending_provisioning",
|
||||
created_at: "2026-01-01T00:00:00Z",
|
||||
},
|
||||
@@ -182,7 +181,7 @@ Deno.test("profile settings parser rejects missing, mistyped, stale, and invalid
|
||||
);
|
||||
|
||||
const mistyped = profileSettingsFixture();
|
||||
mistyped.config_revision = "7";
|
||||
mistyped.tree_digest = 7;
|
||||
assertThrows(
|
||||
() => parseProfileSettingsResponse(mistyped),
|
||||
ProfileApiError,
|
||||
@@ -212,7 +211,6 @@ Deno.test("Workspace signing identity parser validates active and pending public
|
||||
algorithm: "ed25519",
|
||||
public_key: "public-key",
|
||||
public_key_fingerprint: "sha256:fingerprint",
|
||||
revision: 1,
|
||||
state: "active",
|
||||
created_at: "2026-01-01T00:00:00Z",
|
||||
provisioned_at: "2026-01-01T00:00:00Z",
|
||||
@@ -224,7 +222,6 @@ Deno.test("Workspace signing identity parser validates active and pending public
|
||||
algorithm: "ed25519",
|
||||
public_key: "public-key",
|
||||
public_key_fingerprint: "sha256:fingerprint",
|
||||
revision: 1,
|
||||
},
|
||||
};
|
||||
assertEquals(
|
||||
@@ -237,7 +234,6 @@ Deno.test("Workspace signing identity parser validates active and pending public
|
||||
workspace_id: "workspace-1",
|
||||
key_id: "WK-1",
|
||||
algorithm: "ed25519",
|
||||
revision: 1,
|
||||
state: "pending_provisioning",
|
||||
created_at: "2026-01-01T00:00:00Z",
|
||||
},
|
||||
|
||||
@@ -13,7 +13,6 @@ const repository = {
|
||||
kind: "git",
|
||||
provider: "git",
|
||||
source: { kind: "local_path", uri: "/srv/main" },
|
||||
source_revision: 1,
|
||||
source_fingerprint: "sha256:main",
|
||||
observed_status: "ready",
|
||||
record_authority: "workspace-control-plane",
|
||||
|
||||
@@ -19,7 +19,7 @@ function compatibleResponse(): Record<string, unknown> {
|
||||
return {
|
||||
workspace_id: "workspace-a",
|
||||
runtime_id: "runtime-a",
|
||||
binding_revision: 3,
|
||||
binding_id: "binding-3",
|
||||
connection_state: "verified",
|
||||
verification: null,
|
||||
checked_at: "2026-09-01T12:00:00Z",
|
||||
@@ -67,12 +67,11 @@ Deno.test("runtime connection response rejects unknown fields and incoherent com
|
||||
verified_at: "2026-09-01T12:00:00Z",
|
||||
last_checked_at: "2026-09-01T12:00:01Z",
|
||||
last_outcome: "verified",
|
||||
binding_revision: 2,
|
||||
binding_id: "binding-2",
|
||||
workspace_key_id: "WK-a",
|
||||
workspace_identity_revision: 1,
|
||||
workspace_trust_generation: 1,
|
||||
workspace_public_key_fingerprint: "sha256:workspace",
|
||||
workspace_trust_id: "trust-a",
|
||||
runtime_public_key_fingerprint: "sha256:runtime",
|
||||
runtime_identity_revision: 1,
|
||||
},
|
||||
}),
|
||||
null,
|
||||
|
||||
@@ -181,7 +181,7 @@ Deno.test("Runtime detail keeps trust controls owner-only and conflict-safe", as
|
||||
"new RuntimeRemovalAttempt()",
|
||||
"runtimeRemovalAttempt.operationId()",
|
||||
"operation_id: operationId",
|
||||
"expected_binding_revision: trust.revision",
|
||||
"expected_binding_id: trust.binding_id",
|
||||
"The Backend removes Workspace trust and this Runtime registration as one guarded operation.",
|
||||
"deleteRuntimeConfirmation.trim() !== data.runtimeId",
|
||||
"Delete Runtime registration",
|
||||
|
||||
@@ -46,19 +46,18 @@ function runtime() {
|
||||
binding: {
|
||||
state: "verified",
|
||||
connection_state: "verified",
|
||||
revision: 3,
|
||||
binding_id: "binding-3",
|
||||
workspace_key_id: "WK-1",
|
||||
workspace_key_generation: 1,
|
||||
workspace_trust_id: "trust-1",
|
||||
verification: {
|
||||
verified_at: "2026-09-01T13:00:00Z",
|
||||
last_checked_at: "2026-09-01T13:00:00Z",
|
||||
last_outcome: "verified",
|
||||
binding_revision: 3,
|
||||
binding_id: "binding-3",
|
||||
workspace_key_id: "WK-1",
|
||||
workspace_identity_revision: 1,
|
||||
workspace_trust_generation: 1,
|
||||
workspace_public_key_fingerprint: "sha256:workspace-key",
|
||||
workspace_trust_id: "trust-1",
|
||||
runtime_public_key_fingerprint: "SHA256:current",
|
||||
runtime_identity_revision: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -88,7 +87,7 @@ function detail() {
|
||||
trust_key: {
|
||||
status: "active",
|
||||
fingerprint: "SHA256:current",
|
||||
revision: 3,
|
||||
binding_id: "binding-3",
|
||||
created_at: "2026-09-01T12:00:00Z",
|
||||
updated_at: "2026-09-01T13:00:00Z",
|
||||
revoked_at: null,
|
||||
@@ -98,7 +97,7 @@ function detail() {
|
||||
actor_account_id: "account-a",
|
||||
old_fingerprint: null,
|
||||
new_fingerprint: "SHA256:current",
|
||||
revision: 3,
|
||||
binding_id: "binding-3",
|
||||
at: "2026-09-01T13:00:00Z",
|
||||
}],
|
||||
};
|
||||
@@ -124,12 +123,12 @@ Deno.test("Runtime list and detail parsers return generated Runtime DTO shapes",
|
||||
|
||||
const parsed = parseWorkspaceRuntimeDetail(detail());
|
||||
assert(
|
||||
parsed.trust_key.revision === 3,
|
||||
"revision was not preserved as a safe integer",
|
||||
parsed.trust_key.binding_id === "binding-3",
|
||||
"binding identity was not preserved",
|
||||
);
|
||||
assert(
|
||||
parsed.recent_audit[0]?.revision === 3,
|
||||
"audit revision was not normalized",
|
||||
parsed.recent_audit[0]?.binding_id === "binding-3",
|
||||
"audit binding_id was not normalized",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -144,7 +143,7 @@ Deno.test("Runtime list parser accepts the built-in Runtime's internal binding",
|
||||
typeof embedded.management.binding
|
||||
>;
|
||||
delete binding.workspace_key_id;
|
||||
delete binding.workspace_key_generation;
|
||||
delete binding.workspace_trust_id;
|
||||
delete binding.verification;
|
||||
|
||||
const list = parseWorkspaceRuntimeList({
|
||||
@@ -166,7 +165,7 @@ Deno.test("Runtime management parser rejects Workspace identity bindings without
|
||||
typeof payload.runtime.management.binding
|
||||
>;
|
||||
delete binding.workspace_key_id;
|
||||
delete binding.workspace_key_generation;
|
||||
delete binding.workspace_trust_id;
|
||||
binding.state = "configured";
|
||||
assertThrows(
|
||||
() => parseWorkspaceRuntimeDetail(payload),
|
||||
@@ -192,19 +191,19 @@ Deno.test("Runtime validators reject unknown object keys and enum variants", ()
|
||||
parseRuntimeTrustConflict({
|
||||
error: "future_conflict",
|
||||
message: "conflict",
|
||||
current_revision: 4,
|
||||
current_binding_id: "binding-4",
|
||||
current_fingerprint: "SHA256:new",
|
||||
}),
|
||||
"contains an unknown enum value",
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("Runtime validators reject unsafe revisions and bounded collection overflow", () => {
|
||||
Deno.test("Runtime validators reject invalid binding identities and bounded collection overflow", () => {
|
||||
const unsafeRevision = structuredClone(detail());
|
||||
unsafeRevision.trust_key.revision = Number.MAX_SAFE_INTEGER + 1;
|
||||
Object.assign(unsafeRevision.trust_key, { binding_id: 1 });
|
||||
assertThrows(
|
||||
() => parseWorkspaceRuntimeDetail(unsafeRevision),
|
||||
"must be a safe integer",
|
||||
"must be a string",
|
||||
);
|
||||
|
||||
const tooMuchAudit = structuredClone(detail());
|
||||
@@ -263,7 +262,7 @@ Deno.test("mismatched revoke fingerprint never sends a request", async () => {
|
||||
await revokeRuntimeTrustKey(
|
||||
"workspace-a",
|
||||
"runtime-a",
|
||||
{ expected_revision: 3 },
|
||||
{ expected_binding_id: "binding-3" },
|
||||
"sha256:current",
|
||||
"sha256:different",
|
||||
fetchImpl,
|
||||
@@ -325,7 +324,7 @@ Deno.test("Runtime removal attempt retains its id across response-loss retry", a
|
||||
const operation = await removeRemoteRuntime(
|
||||
"workspace-a",
|
||||
"runtime-a",
|
||||
{ operation_id: operationId, expected_binding_revision: 4 },
|
||||
{ operation_id: operationId, expected_binding_id: "binding-4" },
|
||||
() => {
|
||||
calls += 1;
|
||||
if (calls === 1) return Promise.reject(new Error("response lost"));
|
||||
@@ -391,7 +390,7 @@ Deno.test("Runtime removal uses the Workspace-scoped operation route", async ()
|
||||
const operation = await removeRemoteRuntime(
|
||||
"workspace a",
|
||||
"runtime/a",
|
||||
{ operation_id: "remove-runtime-a", expected_binding_revision: 7 },
|
||||
{ operation_id: "remove-runtime-a", expected_binding_id: "binding-7" },
|
||||
fetchImpl,
|
||||
);
|
||||
|
||||
@@ -403,7 +402,7 @@ Deno.test("Runtime removal uses the Workspace-scoped operation route", async ()
|
||||
assert(
|
||||
JSON.stringify(requestedBody) === JSON.stringify({
|
||||
operation_id: "remove-runtime-a",
|
||||
expected_binding_revision: 7,
|
||||
expected_binding_id: "binding-7",
|
||||
}),
|
||||
"Runtime removal request body drifted",
|
||||
);
|
||||
@@ -451,6 +450,85 @@ Deno.test("Runtime public key preview matches the Server fingerprint contract",
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("Runtime registration rejects empty or oversized enrollment identities before sending", async () => {
|
||||
for (const enrollment of ["", "x".repeat(257)]) {
|
||||
let calls = 0;
|
||||
try {
|
||||
await createRemoteRuntime("workspace-a", {
|
||||
public_bundle: { identity_id: "runtime-a", public_key: "test-key" },
|
||||
workspace_trust_id: enrollment,
|
||||
endpoint: "https://runtime.example",
|
||||
}, (() => { calls++; throw new Error("unexpected request"); }) as typeof fetch);
|
||||
throw new Error("expected enrollment validation to reject");
|
||||
} catch (error) {
|
||||
assert(error instanceof Error && error.message.includes("workspace_trust_id"), "enrollment validation was bypassed");
|
||||
}
|
||||
assert(calls === 0, "invalid enrollment reached the Server");
|
||||
}
|
||||
});
|
||||
|
||||
Deno.test("Runtime trust replacement preserves binding conflicts without retrying", async () => {
|
||||
for (const errorKind of ["stale_binding", "fingerprint_in_use"] as const) {
|
||||
const request = {
|
||||
public_bundle: {
|
||||
identity_id: "runtime-a",
|
||||
public_key: "yoi-ed25519-pub:v1:test",
|
||||
},
|
||||
display_name: null,
|
||||
endpoint: "https://runtime.example",
|
||||
workspace_trust_id: "runtime-issued-enrollment-id",
|
||||
expected_binding_id: "observed-binding-id",
|
||||
};
|
||||
const conflict = {
|
||||
error: errorKind,
|
||||
message: "Reload the authoritative Runtime trust.",
|
||||
current_binding_id: "current-binding-id",
|
||||
current_fingerprint: "sha256:current-key",
|
||||
};
|
||||
let calls = 0;
|
||||
const fetchImpl = ((input: RequestInfo | URL, init?: RequestInit) => {
|
||||
calls++;
|
||||
assert(String(input) === "/api/w/workspace-a/runtimes", "wrong trust route");
|
||||
assert(init?.body === JSON.stringify(request), "observed binding ID changed");
|
||||
return Promise.resolve(Response.json(conflict, { status: 409 }));
|
||||
}) as typeof fetch;
|
||||
try {
|
||||
await createRemoteRuntime("workspace-a", request, fetchImpl);
|
||||
throw new Error("expected trust replacement to reject");
|
||||
} catch (error) {
|
||||
assert(error instanceof RuntimeTrustConflictError, "binding conflict was hidden");
|
||||
assert(
|
||||
JSON.stringify(error.conflict) === JSON.stringify(conflict),
|
||||
"authoritative conflict identity was changed",
|
||||
);
|
||||
}
|
||||
assert(calls === 1, "trust replacement was retried");
|
||||
}
|
||||
});
|
||||
|
||||
Deno.test("Runtime trust replacement rejects legacy counter conflict responses", async () => {
|
||||
const fetchImpl = (() => Promise.resolve(Response.json({
|
||||
error: "stale_binding",
|
||||
message: "Stale trust",
|
||||
current_revision: 3,
|
||||
}, { status: 409 }))) as typeof fetch;
|
||||
try {
|
||||
await createRemoteRuntime("workspace-a", {
|
||||
public_bundle: { identity_id: "runtime-a", public_key: "test-key" },
|
||||
endpoint: "https://runtime.example",
|
||||
workspace_trust_id: "runtime-issued-enrollment-id",
|
||||
expected_binding_id: "observed-binding-id",
|
||||
}, fetchImpl);
|
||||
throw new Error("expected malformed conflict to reject");
|
||||
} catch (error) {
|
||||
assert(!(error instanceof RuntimeTrustConflictError), "legacy conflict was accepted");
|
||||
assert(
|
||||
error instanceof Error && error.message === "Runtime trust conflict response was invalid",
|
||||
"malformed conflict did not fail closed",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
Deno.test("Runtime create surfaces bounded Settings error details", async () => {
|
||||
const fetchImpl = (() =>
|
||||
Promise.resolve(
|
||||
@@ -473,7 +551,8 @@ Deno.test("Runtime create surfaces bounded Settings error details", async () =>
|
||||
},
|
||||
display_name: null,
|
||||
endpoint: "https://runtime.example",
|
||||
expected_revision: null,
|
||||
workspace_trust_id: "runtime-issued-enrollment-id",
|
||||
expected_binding_id: null,
|
||||
},
|
||||
fetchImpl,
|
||||
);
|
||||
|
||||
@@ -32,7 +32,7 @@ function plan() {
|
||||
workspace_id: "workspace-a",
|
||||
runtime_id: "runtime-a",
|
||||
generated_at: "2026-09-01T00:00:00Z",
|
||||
revision: "revision-a",
|
||||
|
||||
digest: "digest-a",
|
||||
workers: [{
|
||||
target_id: "worker-target",
|
||||
|
||||
@@ -53,7 +53,6 @@ function workspaceProvenance() {
|
||||
kind: "workspace",
|
||||
id: "workspace:release",
|
||||
virtual_path: "skills/release/SKILL.md",
|
||||
revision: 42,
|
||||
source_digest: "workspace-source-digest",
|
||||
tree_digest: "tree-digest",
|
||||
};
|
||||
@@ -62,7 +61,7 @@ function workspaceProvenance() {
|
||||
function catalogFixture(): Record<string, unknown> {
|
||||
return {
|
||||
authority: "workspace-config-skills-v1",
|
||||
projection: { config_revision: 42, tree_digest: "tree-digest" },
|
||||
projection: { tree_digest: "tree-digest" },
|
||||
entries: [{
|
||||
name: "errors",
|
||||
description: "Builtin guidance",
|
||||
@@ -92,7 +91,7 @@ function catalogFixture(): Record<string, unknown> {
|
||||
function detailFixture(): Record<string, unknown> {
|
||||
return {
|
||||
authority: "workspace-config-skills-v1",
|
||||
projection: { config_revision: 42, tree_digest: "tree-digest" },
|
||||
projection: { tree_digest: "tree-digest" },
|
||||
name: "release",
|
||||
description: "Workspace guidance",
|
||||
provenance: workspaceProvenance(),
|
||||
@@ -117,7 +116,7 @@ Deno.test("Skill catalog parser accepts generated builtin, Workspace, and invali
|
||||
assertEquals(parsed.entries[0].provenance.kind, "builtin");
|
||||
assertEquals(parsed.entries[1].activation_status, "inactive");
|
||||
assertEquals(parsed.entries[1].projection_status, "invalid");
|
||||
assertEquals(parsed.projection.config_revision, 42);
|
||||
assertEquals(parsed.projection.tree_digest, "tree-digest");
|
||||
});
|
||||
|
||||
Deno.test("Skill detail parser preserves shared generated DTO fields", () => {
|
||||
@@ -127,13 +126,7 @@ Deno.test("Skill detail parser preserves shared generated DTO fields", () => {
|
||||
assertEquals(parsed.resources[0].supported, true);
|
||||
});
|
||||
|
||||
Deno.test("Skill parser rejects stale Workspace projection revision and digest", () => {
|
||||
const staleRevision = catalogFixture();
|
||||
(staleRevision.projection as Record<string, unknown>).config_revision = 43;
|
||||
assertContractError(
|
||||
() => parseSkillCatalogResponse(staleRevision),
|
||||
"stale Workspace Skill projection",
|
||||
);
|
||||
Deno.test("Skill parser rejects stale Workspace projection digest", () => {
|
||||
|
||||
const staleDigest = catalogFixture();
|
||||
(staleDigest.projection as Record<string, unknown>).tree_digest = "new-tree";
|
||||
@@ -168,13 +161,12 @@ Deno.test("Skill parser fails closed on unknown fields and newer enum values", (
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("Skill parser rejects unsafe revisions and oversized collections or strings", () => {
|
||||
const unsafeRevision = catalogFixture();
|
||||
(unsafeRevision.projection as Record<string, unknown>).config_revision =
|
||||
Number.MAX_SAFE_INTEGER + 1;
|
||||
Deno.test("Skill parser rejects removed revision fields and oversized collections or strings", () => {
|
||||
const legacyRevision = catalogFixture();
|
||||
(legacyRevision.projection as Record<string, unknown>).config_revision = 1;
|
||||
assertContractError(
|
||||
() => parseSkillCatalogResponse(unsafeRevision),
|
||||
"safe integer",
|
||||
() => parseSkillCatalogResponse(legacyRevision),
|
||||
"unknown fields",
|
||||
);
|
||||
|
||||
const oversizedCatalog = catalogFixture();
|
||||
|
||||
@@ -82,7 +82,6 @@ Deno.test("workspace catalog enriches each visible workspace without dropping si
|
||||
kind: "git",
|
||||
provider: "git",
|
||||
source: { kind: "local_path", uri: "/srv/alpha" },
|
||||
source_revision: 1,
|
||||
source_fingerprint: "sha256:alpha",
|
||||
observed_status: "ready",
|
||||
default_selector: "develop",
|
||||
|
||||
@@ -76,7 +76,6 @@ const repositoryList = {
|
||||
kind: "git",
|
||||
provider: "git",
|
||||
source: { kind: "local_path", uri: "/srv/alpha" },
|
||||
source_revision: 1,
|
||||
source_fingerprint: "sha256:alpha",
|
||||
observed_status: "ready",
|
||||
record_authority: "workspace-control-plane",
|
||||
@@ -153,21 +152,13 @@ Deno.test("stale repository aliases fail closed at the JSON boundary", () => {
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("repository source revisions enforce the OpenAPI integer range", () => {
|
||||
const negative = structuredClone(repositoryList) as Record<string, unknown>;
|
||||
const negativeItems = negative.items as Array<Record<string, unknown>>;
|
||||
negativeItems[0].source_revision = -1;
|
||||
Deno.test("repository JSON rejects removed source revision counters", () => {
|
||||
const stale = structuredClone(repositoryList) as Record<string, unknown>;
|
||||
const items = stale.items as Array<Record<string, unknown>>;
|
||||
items[0].source_revision = 1;
|
||||
assertThrows(
|
||||
() => parseRepositoryListResponse(negative),
|
||||
"must be between 0 and Number.MAX_SAFE_INTEGER",
|
||||
);
|
||||
|
||||
const unsafe = structuredClone(repositoryList) as Record<string, unknown>;
|
||||
const unsafeItems = unsafe.items as Array<Record<string, unknown>>;
|
||||
unsafeItems[0].source_revision = Number.MAX_SAFE_INTEGER + 1;
|
||||
assertThrows(
|
||||
() => parseRepositoryListResponse(unsafe),
|
||||
"must be a safe integer",
|
||||
() => parseRepositoryListResponse(stale),
|
||||
".source_revision is not part of the wire contract",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -285,7 +276,7 @@ Deno.test("Workspace deletion DTOs fail closed and preserve durable operation st
|
||||
const preflight = parseWorkspaceDeletionPreflightResponse({
|
||||
workspace_id: "workspace-a",
|
||||
display_name: "Alpha",
|
||||
expected_revision: "2026-01-01T00:00:00Z",
|
||||
expected_workspace_updated_at: "2026-01-01T00:00:00Z",
|
||||
can_delete: true,
|
||||
resources: {
|
||||
workers: 2,
|
||||
|
||||
Reference in New Issue
Block a user