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
Original file line number Diff line number Diff line change
Expand Up @@ -1362,9 +1362,31 @@ relevant contract, exact implementation boundary, and validation evidence.
| Stage 2B PostgreSQL candidate | PostgreSQL store/RLS conformance, without runtime promotion |
| Stage 2C runtime shadow | Parity, read-candidate, bootstrap, rollback, cutover kernel, and writer fence |
| Stage 2 slice | Reference aggregate/provider implementation and initial NoKV evidence |
| Stage 1 semantic transaction core (#4280) | Shared strict transaction decode, clone isolation, revision projection, and file/NoKV parity fixture |
| Stage 3 slice | Recoverable lifecycle, retention findings, and live provider limits |
| Stage-ladder evidence | Executable stage claims, environment gates, and pending rows |

#### Stage 1 semantic transaction core (#4280)

The file and NoKV adapters now consume one executable semantic core at
`loopx/control_plane/coordination/authority_store_transactions.ts`. It owns the
exact committed-transaction key set, strict JSON/object-list validation,
canonicalization, explicit structured cloning, and the logical
`transactionForRevision` projection. Provider envelopes, storage generations,
failure mapping, and provider-specific revision salts remain in their owning
adapters. This removes duplicated semantic knowledge without creating another
authority writer or changing the default authority source. SQLite and
PostgreSQL row/envelope migration remain later provider stages.

The public fixture in
`tests/control_plane_ts/authority_store_transactions.test.ts` runs native,
reordered legacy-compatible, unknown-key, malformed-list, malformed-nested,
and non-string-identity records through the shared decoder and both active file
and NoKV read paths. It also proves that scan results are isolated clones and
that provider metadata is absent from the logical revision projection. This is
Stage 1 parity evidence, not provider promotion or a claim that all later
provider profiles are qualified.

#### Stage 2C observation foundation: local post-commit capture

The first half of Stage 2C is an explicit, default-off product path. Preview
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1084,9 +1084,28 @@ Stage 3/4 qualification 必须保持以下 ownership 与 proof 边界:
| Stage 2B PostgreSQL candidate | PostgreSQL store/RLS conformance,不代表 runtime promotion |
| Stage 2C runtime shadow | parity、read-candidate、bootstrap、rollback、cutover kernel 与 writer fence |
| Stage 2 slice | reference aggregate/provider 实现与初步 NoKV 证据 |
| Stage 1 semantic transaction core (#4280) | 共享严格 transaction 解码、clone 隔离、revision 投影,以及 file/NoKV parity fixture |
| Stage 3 slice | 可恢复 lifecycle、retention 结论与 live provider 限制 |
| Stage-ladder evidence | 可执行 stage claim、环境 gate 与 pending row |

#### Stage 1 semantic transaction core(#4280):共享 transaction 语义核心

file 与 NoKV adapter 现在共同使用
`loopx/control_plane/coordination/authority_store_transactions.ts`。该模块负责
committed transaction 的精确顶层 key 集合、严格 JSON/object-list 校验、canonicalization、
显式 structured clone,以及逻辑 `transactionForRevision` 投影。provider envelope、
storage generation、failure mapping 与 provider-specific revision salt 仍由各自 adapter
负责。这样消除了重复的语义知识,但没有增加新的 authority writer,也没有改变默认的
authority source。SQLite 与 PostgreSQL 的 row/envelope 迁移仍属于后续 provider stage。

公开 fixture 位于
`tests/control_plane_ts/authority_store_transactions.test.ts`,会把 native、reordered
legacy-compatible、unknown-key、malformed-list、malformed-nested 与 non-string identity
记录同时送入 shared decoder 以及当前两个 active provider 的 file/NoKV read path。它还
验证 scan 结果是隔离 clone,并验证 provider metadata 不会进入 logical revision projection。
这些是 Stage 1 parity 证据,不代表 provider promotion,也不代表后续 provider profile
已经完成资格化。

#### Stage 2C 观察基础:本地提交后 capture

Stage 2C 的前半段是一个显式开启、默认关闭的产品路径。先预览,再开启:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { JsonObject } from "../effect_program.ts";
import type { AuthorityStoreCommittedTransaction } from "./authority_store.ts";
import {
AuthorityStoreProtocolError,
canonicalAuthorityObject,
canonicalAuthorityObjectList,
hasExactAuthorityKeys,
isAuthorityJsonObject,
requireAuthorityStoreId,
} from "./authority_store_codec.ts";

/** Shared wire decoder used by every authority provider. */
export function decodeAuthorityTransaction(value: unknown): AuthorityStoreCommittedTransaction {
if (!isAuthorityJsonObject(value) || !hasExactAuthorityKeys(value, [
"cursor", "provider_revision", "operation_id", "events", "projection", "receipts",
])) throw new AuthorityStoreProtocolError("committed transaction is invalid");
return {
cursor: requireAuthorityStoreId(value.cursor, "transaction cursor"),
provider_revision: requireAuthorityStoreId(value.provider_revision, "transaction provider revision"),
operation_id: requireAuthorityStoreId(value.operation_id, "operation id"),
events: canonicalAuthorityObjectList(value.events, "transaction events"),
projection: canonicalAuthorityObject(value.projection, "transaction projection"),
receipts: canonicalAuthorityObjectList(value.receipts, "transaction receipts"),
};
}

export function cloneAuthorityTransaction(value: AuthorityStoreCommittedTransaction): AuthorityStoreCommittedTransaction {
return structuredClone(value);
}

export function transactionForRevision(value: AuthorityStoreCommittedTransaction): JsonObject {
return {
cursor: value.cursor,
operation_id: value.operation_id,
events: value.events,
projection: value.projection,
receipts: value.receipts,
};
}
65 changes: 10 additions & 55 deletions loopx/control_plane/coordination/file_authority_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,17 @@
} from "./authority_store.ts";
import {
AuthorityStoreProtocolError,
isAuthorityJsonObject,
hasExactAuthorityKeys,
canonicalAuthorityObjectList,

Check warning on line 23 in loopx/control_plane/coordination/file_authority_store.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this unused import of 'canonicalAuthorityObjectList'.

See more on https://sonarcloud.io/project/issues?id=huangruiteng_loopx&issues=AaCUyWQpyyKf7W-fMwMn&open=AaCUyWQpyyKf7W-fMwMn&pullRequest=4280
canonicalAuthorityObject,
authorityUnicodeCompare,
canonicalAuthorityBytes,
canonicalAuthorityObject,
canonicalAuthorityObjectList,
hasExactAuthorityKeys,
isAuthorityJsonObject,
normalizeAuthorityStoreCommit,
parseAuthorityCursor,
requireAuthorityStoreId,
} from "./authority_store_codec.ts";
import { cloneAuthorityTransaction, decodeAuthorityTransaction, transactionForRevision } from "./authority_store_transactions.ts";

const FILE_AUTHORITY_STORE_SCHEMA = "loopx_file_authority_store_v0";
const STORE_IDENTITY_PATTERN = /^file:[0-9a-f]{32}$/;
Expand Down Expand Up @@ -77,37 +78,8 @@
reason: string;
};

function cloneTransaction(
value: AuthorityStoreCommittedTransaction,
): AuthorityStoreCommittedTransaction {
return structuredClone(value);
}

function transactionWithoutRevision(value: AuthorityStoreCommittedTransaction) {
return {
cursor: value.cursor,
operation_id: value.operation_id,
events: value.events,
projection: value.projection,
receipts: value.receipts,
};
}

function providerRevision(
goalId: string,
storeIdentity: string,
previousRevision: string | null,
transaction: ReturnType<typeof transactionWithoutRevision>,
): string {
const digest = createHash("sha256")
.update(canonicalAuthorityBytes({
goal_id: goalId,
store_identity: storeIdentity,
previous_provider_revision: previousRevision,
transaction,
}))
.digest("hex")
.slice(0, 24);
function providerRevision(goalId: string, storeIdentity: string, previousRevision: string | null, transaction: ReturnType<typeof transactionForRevision>): string {
const digest = createHash("sha256").update(canonicalAuthorityBytes({ goal_id: goalId, store_identity: storeIdentity, previous_provider_revision: previousRevision, transaction })).digest("hex").slice(0, 24);
return `file:${transaction.cursor}:${digest}`;
}

Expand Down Expand Up @@ -140,23 +112,6 @@
}
}

function decodeTransaction(value: unknown): AuthorityStoreCommittedTransaction {
if (!isAuthorityJsonObject(value) || !hasExactAuthorityKeys(value, [
"cursor", "provider_revision", "operation_id", "events", "projection", "receipts",
])) throw new AuthorityStoreProtocolError("committed transaction is invalid");
return {
cursor: requireAuthorityStoreId(value.cursor, "transaction cursor"),
provider_revision: requireAuthorityStoreId(
value.provider_revision,
"transaction provider revision",
),
operation_id: requireAuthorityStoreId(value.operation_id, "operation id"),
events: canonicalAuthorityObjectList(value.events, "transaction events"),
projection: canonicalAuthorityObject(value.projection, "transaction projection"),
receipts: canonicalAuthorityObjectList(value.receipts, "transaction receipts"),
};
}

function decodeDocument(
value: unknown,
goalId: string,
Expand All @@ -178,7 +133,7 @@
if (!Array.isArray(value.committed)) {
throw new AuthorityStoreProtocolError("file authority store history is invalid");
}
const committed = value.committed.map(decodeTransaction);
const committed = value.committed.map(decodeAuthorityTransaction);
if (committed.length === 0 || parseAuthorityCursor(cursor) !== BigInt(committed.length)) {
throw new AuthorityStoreProtocolError("file authority store lineage is invalid");
}
Expand All @@ -196,7 +151,7 @@
goalId,
storeIdentity,
previousRevision,
transactionWithoutRevision(entry),
transactionForRevision(entry),
);
if (entry.provider_revision !== expectedRevision) {
throw new AuthorityStoreProtocolError("file authority store revision lineage is invalid");
Expand Down Expand Up @@ -495,7 +450,7 @@
};
}
const start = Number(offset);
const transactions = document.committed.slice(start, start + limit).map(cloneTransaction);
const transactions = document.committed.slice(start, start + limit).map(cloneAuthorityTransaction);
return {
status: "page",
transactions,
Expand Down
72 changes: 10 additions & 62 deletions loopx/control_plane/coordination/nokv_authority_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,16 @@
} from "./authority_store.ts";
import {
AuthorityStoreProtocolError,
canonicalAuthorityBytes,
canonicalAuthorityObject,
canonicalAuthorityObjectList,
hasExactAuthorityKeys,
isAuthorityJsonObject,
hasExactAuthorityKeys,
canonicalAuthorityObjectList,

Check warning on line 20 in loopx/control_plane/coordination/nokv_authority_store.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this unused import of 'canonicalAuthorityObjectList'.

See more on https://sonarcloud.io/project/issues?id=huangruiteng_loopx&issues=AaCUyWhRyyKf7W-fMwMo&open=AaCUyWhRyyKf7W-fMwMo&pullRequest=4280
canonicalAuthorityObject,
canonicalAuthorityBytes,
normalizeAuthorityStoreCommit,
parseAuthorityCursor,
requireAuthorityStoreId,
} from "./authority_store_codec.ts";
import { cloneAuthorityTransaction, decodeAuthorityTransaction, transactionForRevision } from "./authority_store_transactions.ts";

const NOKV_AUTHORITY_STORE_SCHEMA = "loopx_nokv_authority_store_v0";
const DEFAULT_MAX_ENVELOPE_BYTES = 16 * 1024 * 1024;
Expand Down Expand Up @@ -98,42 +99,8 @@
| { status: "missing"; identity: string }
| AuthorityStoreReadFailure;

function cloneTransaction(
value: AuthorityStoreCommittedTransaction,
): AuthorityStoreCommittedTransaction {
return structuredClone(value);
}

function transactionWithoutRevision(value: AuthorityStoreCommittedTransaction) {
return {
cursor: value.cursor,
operation_id: value.operation_id,
events: value.events,
projection: value.projection,
receipts: value.receipts,
};
}

function providerRevision(
tenantId: string,
goalId: string,
storeIdentity: string,
storageGeneration: number,
previousRevision: string | null,
transaction: ReturnType<typeof transactionWithoutRevision>,
): string {
const digest = createHash("sha256")
.update(canonicalAuthorityBytes({
provider: "nokv",
tenant_id: tenantId,
goal_id: goalId,
store_identity: storeIdentity,
storage_generation: storageGeneration,
previous_provider_revision: previousRevision,
transaction,
}))
.digest("hex")
.slice(0, 24);
function providerRevision(tenantId: string, goalId: string, storeIdentity: string, storageGeneration: number, previousRevision: string | null, transaction: ReturnType<typeof transactionForRevision>): string {
const digest = createHash("sha256").update(canonicalAuthorityBytes({ provider: "nokv", tenant_id: tenantId, goal_id: goalId, store_identity: storeIdentity, storage_generation: storageGeneration, previous_provider_revision: previousRevision, transaction })).digest("hex").slice(0, 24);
return `nokv:${transaction.cursor}:${digest}`;
}

Expand Down Expand Up @@ -164,25 +131,6 @@
return value as number;
}

function decodeTransaction(value: unknown): AuthorityStoreCommittedTransaction {
if (!isAuthorityJsonObject(value) || !hasExactAuthorityKeys(value, [
"cursor", "provider_revision", "operation_id", "events", "projection", "receipts",
])) {
throw new AuthorityStoreProtocolError("committed transaction is invalid");
}
return {
cursor: requireAuthorityStoreId(value.cursor, "transaction cursor"),
provider_revision: requireAuthorityStoreId(
value.provider_revision,
"transaction provider revision",
),
operation_id: requireAuthorityStoreId(value.operation_id, "operation id"),
events: canonicalAuthorityObjectList(value.events, "transaction events"),
projection: canonicalAuthorityObject(value.projection, "transaction projection"),
receipts: canonicalAuthorityObjectList(value.receipts, "transaction receipts"),
};
}

function decodeDocument(
value: unknown,
tenantId: string,
Expand Down Expand Up @@ -220,7 +168,7 @@
if (!Array.isArray(value.committed)) {
throw new AuthorityStoreProtocolError("NoKV authority store history is invalid");
}
const committed = value.committed.map(decodeTransaction);
const committed = value.committed.map(decodeAuthorityTransaction);
if (
committed.length === 0 ||
storageGeneration !== committed.length ||
Expand All @@ -247,7 +195,7 @@
storeIdentity,
generation,
previousRevision,
transactionWithoutRevision(entry),
transactionForRevision(entry),
);
if (entry.provider_revision !== expectedRevision) {
throw new AuthorityStoreProtocolError("NoKV authority store revision lineage is invalid");
Expand Down Expand Up @@ -660,7 +608,7 @@
const start = Number(offset);
const transactions = result.document.committed
.slice(start, start + limit)
.map(cloneTransaction);
.map(cloneAuthorityTransaction);
return {
status: "page",
transactions,
Expand Down
Loading
Loading