You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[finding] docs: content/docs/kernel/cluster.mdx describes the cluster-invalidation family as metadata.changed only — the metadata.mutated (#13331) and datasource.mutated (#13805) lanes are undocumented #14339
Found while implementing #13805 (the driver registry's adopter of the #13331 cluster-invalidation family). Filed unassigned as an observation; nothing here is fixed by that card's PR.
What the docs say today
content/docs/kernel/cluster.mdx §6.2 ("The metadata change event") and §7.3 describe cross-node invalidation as ONE channel, metadata.changed, handed to MetadataManager by MetadataClusterBridgePlugin. Measured on main at 909a4417: grep -rn "metadata.mutated\|datasource.mutated" content/docs returns zero hits.
What the tree does
MetadataClusterBridgePlugin (packages/services/service-cluster/src/metadata-cluster-bridge-plugin.ts) late-binds THREE independent lanes at kernel:ready, and only the first is on the docs page:
metadata.changed — metadata SERVICE cache invalidation (documented, §6.2).
The payload contract the two newer lanes share (address-only signal, receipt = convergence from the replica's own read, originNode loopback suppression, at-most-once delivery bounded by boot rehydration) is stated in each channel's source docblock and changeset, but a reader of the kernel cluster page — the page the bridge plugin's own docblock points at — is told the family has one member.
Why it is filed rather than fixed on #13805's branch
#13805's dispatch scoped docs to "a sentence beside metadata.mutated" — and there is no metadata.mutated sentence to sit beside. Documenting one of the two missing lanes alone would leave the page describing two of three; documenting lane 2 is #13331's surface, not #13805's. The right fix is one docs-only change that describes the family (the shared contract above) and lists its three adopters with their state owners.
Suggested direction
A docs-only PR to content/docs/kernel/cluster.mdx: rename/extend §6.2 to "the cluster-invalidation family", keep the metadata.changed description, add the two convergence lanes with a pointer to METADATA_MUTATION_CLUSTER_CHANNEL / ClusterMetadataMutationPayload (@objectstack/metadata-protocol) and DATASOURCE_MUTATION_CLUSTER_CHANNEL / ClusterDatasourceMutationPayload (@objectstack/service-datasource), and fix the bridge plugin's See cluster.mdx §5 pointer (§5 is service scope and leader election; the fan-out lives in §6).
Refs: #13331 (lane 2) · #13805 (lane 3) · #14021 (the in-process guard all three lanes carry, whose docs sentence landed in PR #14228 — the only recent docs edit to this page).
Lands in:content/docs/kernel/cluster.mdx — one docs-only PR, the shape the card prescribes: §6.2 becomes the cluster-invalidation family (shared contract: address-only signal, receipt = convergence from the replica's own read, originNode loopback suppression, at-most-once bounded by boot rehydration), keeps the metadata.changed description, adds the two convergence lanes with their channel constants and payload types named, and fixes the bridge plugin's See cluster.mdx §5 pointer to §6. ⛔ Documenting one lane alone is refused — a page describing two of three is the same defect one member smaller.
Premise check binding on the dev: lane 3 (datasource.mutated, #13805) was in flight when this was filed. Re-read both lanes on the merged tree and document what is there; if #13805 has not landed, document lane 2 and the family, and say in the PR body that lane 3 follows — ⛔ do not describe an unlanded channel as shipped.
Worktree:../objectstack-14339 (dedicated per-task worktree, off origin/main)
Domain:domain:devx (content/docs/** — docs-only)
File surface:content/docs/kernel/cluster.mdx (§6.2 → the cluster-invalidation family, §7.3 cross-reference) and packages/services/service-cluster/src/metadata-cluster-bridge-plugin.ts ONE docblock line (See content/docs/kernel/cluster.mdx §5 → §6; comment text only, no code). No changeset for the docs; if the one-line docblock edit in a published package needs one per the repo's changeset gate the dev adds a @objectstack/service-cluster: patch changeset and says so. ⛔ No other file; ⛔ content/docs/releases/** untouched.
{
"issue": 14339,
"status": "done",
"branch": "claude/issue-14339-cluster-invalidation-family-docs",
"pr": "https://github.com/objectstack-ai/objectstack/pull/14475",
"premise_still_valid": true,
"summary": "content/docs/kernel/cluster.mdx section 6.2 retitled 'The cluster-invalidation family': a summary table (channel, payload type, package, what it converges) plus the shared contract the two newer lanes carry (address-only signal, receipt = convergence from the replica's own read, originNode loopback suppression, at-most-once bounded by boot rehydration, quoted from protocol.ts and datasource-admin-service.ts's own docblocks). The existing metadata.changed description is kept as Lane 1; two new subsections document Lane 2 (metadata.mutated, METADATA_MUTATION_CLUSTER_CHANNEL, ClusterMetadataMutationPayload, packages/metadata-protocol) and Lane 3 (datasource.mutated, DATASOURCE_MUTATION_CLUSTER_CHANNEL, ClusterDatasourceMutationPayload, packages/services/service-datasource), each with a real code excerpt, the receipt logic, and what it converges. Section 7.3 got one cross-reference sentence pointing a long-lived ObjectQL registry cache at lanes 2/3 instead of a redundant metadata.changed subscription. Section numbering (6.3, 7.x) is unchanged; new content lives as new #### sub-parts inside 6.2. metadata-cluster-bridge-plugin.ts line 58's stale 'See cluster.mdx section 5' docblock pointer is fixed to 'section 6.2' (comment text only, no code change). Both lanes were confirmed landed on the merge base (a98b61b3e) before writing, so the triage's 'lane 3 not landed' branch did not apply.",
"tests": "Premise verified on base a98b61b3e: METADATA_MUTATION_CLUSTER_CHANNEL at packages/metadata-protocol/src/protocol.ts:4010 (PayloadType ClusterMetadataMutationPayload:4023), DATASOURCE_MUTATION_CLUSTER_CHANNEL at packages/services/service-datasource/src/datasource-admin-service.ts:71 (payload type :86); MetadataClusterBridgePlugin binds all 3 lanes at kernel:ready (:80-82). git grep -n 'metadata.mutated|datasource.mutated' -- content/docs: 0 hits BEFORE the edit, 7 hits AFTER (2 table rows, 2 lane headings/intros, 1 cross-ref callout). Gate derivation: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands named 44 local gate commands for this diff (28 pnpm, 16 direct node). Ran all 44: 38 RAN-PASS on the first pass. 3 initially exited PREREQUISITE-NOT-MET (their own scripts' language) because a dependency package was unbuilt in the fresh worktree -- built @objectstack/spec (gen:schema + build), @objectstack/lint and @objectstack/formula (their dependency closures via pnpm --filter 'PKG^...' build) through the shared verify lock, then re-ran: pnpm --filter @objectstack/spec run check:docs (RAN-PASS, '229 generated files in sync'), pnpm --filter @objectstack/lint run check:doc-formula-expressions (RAN-PASS), pnpm --filter @objectstack/lint run check:doc-security-posture (RAN-PASS, '27 ObjectSchema.create examples ... carry an os validate-clean security posture'). node scripts/check-test-completeness.mjs exits 3 by design (its own message: 'this gate grades a saved turbo run test log ... arrived here from the dispatch-gates family with NO argument ... the local reading for this gate is NOT MEASURED, not a red') -- recorded NOT MEASURED, no local action possible. pnpm --filter @objectstack/spec run check:skill-examples: after the spec rebuild, the two surfaces this diff could affect passed clean (skills+docs: 233 blocks, spec source TSDoc: 9 blocks, 0 errors); the third surface (client SDK examples, 23 blocks) still failed PREREQUISITE-NOT-MET on unbuilt @objectstack/client-react/@objectstack/client dist -- CI's own typecheck-consumers job builds the WHOLE workspace (turbo run build --filter='./packages/*' --filter='./examples/*^...') before this exact command, so building ~2 more packages' full dependency closures locally for a docs-only diff touching none of the client SDK examples was judged disproportionate; recorded NOT MEASURED for that one surface, RAN-PASS for the two surfaces this diff can affect. pnpm check:dual-build-cjs-loads: PREREQUISITE-NOT-MET, its own message names ~100 unbuilt packages' dist/ across the whole workspace (client-react, client, studio, setup, hono, ...) -- same full-workspace-build precondition CI's own job satisfies first; recorded NOT MEASURED. service-cluster (the docblock-edit package): pnpm --filter '@objectstack/service-cluster^...' build then pnpm exec tsc --noEmit -p packages/services/service-cluster/tsconfig.json: 1 pre-existing error at src/memory/memory.contract.test.ts:26 ('number' not assignable to 'void or a Promise of void' on an Array.push return value used as a subscribe callback) -- confirmed present verbatim on origin/main before this PR's edit (git show origin/main:...memory.contract.test.ts) and unrelated to the one-line docblock comment touched here; the package has no dedicated typecheck/lint script (package.json: build+test only), so this file is not reached by any gate this diff owes. pnpm --filter @objectstack/service-cluster test: RAN-PASS, 7 files / 105 tests, 5.77s. apps/docs (the published docs site): pnpm --filter '@objectstack/docs^...' build && pnpm --filter @objectstack/docs typecheck (fumadocs-mdx + next typegen + tsc --noEmit): RAN-PASS, 0 errors, on the pre-merge tree. After git fetch origin main && git merge origin/main (clean, no conflicts; incoming commits touched packages/spec/src/automation/*.zod.ts and packages/spec/src/data/driver.zod.ts, none of it in cluster.mdx's dependency graph or referenced TSDoc), a second run of the docs typecheck was started through the shared verify lock but the container was heavily contended (a multi-minute queue chain, several other agents' builds ahead); narrowed to NOT re-running it given (a) the pre-merge run was clean, (b) the merge's incoming diff does not touch any file this doc page's content or code excerpts read from, and (c) waiting further would have meant burning another lock cycle for a result already established -- this narrowing is reported, not silent. All commands' exit codes were captured before any pipe (cmd redirected to a logfile with 2 to 1, then echo EXIT=$?), never read off a piped $?.",
"mcp_calls": 0,
"open_questions": [],
"out_of_scope_findings": []
}
ACCEPT — #14339 landed. PR #14475 merged to main as 54f224062 via the merge queue (draft flipped and auto-merge armed 09:00Z after the CI watcher read GREEN on c09fb2cd8; added_to_merge_queue 09:01:32Z).
Close-out probes executed on origin/main (fresh fetch, throwaway worktree — not the shared checkout; 54f224062 is in origin/main's first-parent history at 9c7d9d4b3):
packages/services/service-cluster/src/metadata-cluster-bridge-plugin.ts:58 — the docblock pointer now reads See content/docs/kernel/cluster.mdx §6.2 (was §5).
Docs-only, skip-changeset. pm:dispatched stripped in this same step (type / priority / domain:devx kept — ownership is not state).
Activity
huangyiirene commentedon Sep 2, 2026
Triage (R+90, triage seat, session
session_019kDRpB7D2XzVzkaLp57T5D):pm:queue·priority:p3·domain:devx·documentation· type Task.findingremoved.Lands in:
content/docs/kernel/cluster.mdx— one docs-only PR, the shape the card prescribes: §6.2 becomes the cluster-invalidation family (shared contract: address-only signal, receipt = convergence from the replica's own read,originNodeloopback suppression, at-most-once bounded by boot rehydration), keeps themetadata.changeddescription, adds the two convergence lanes with their channel constants and payload types named, and fixes the bridge plugin'sSee cluster.mdx §5pointer to §6. ⛔ Documenting one lane alone is refused — a page describing two of three is the same defect one member smaller.Premise check binding on the dev: lane 3 (
datasource.mutated, #13805) was in flight when this was filed. Re-read both lanes on the merged tree and document what is there; if #13805 has not landed, document lane 2 and the family, and say in the PR body that lane 3 follows — ⛔ do not describe an unlanded channel as shipped.Size/model suggestion: S,
sonnet/opus.Generated by Claude Code
claude commentedon Sep 2, 2026
Claim: session
session_01WLJQhde67SeTccsmnBVarV(domain:devx execution seat, seat post #6023) — R1 wave 7.claude/issue-14339-cluster-invalidation-family-docs../objectstack-14339(dedicated per-task worktree, offorigin/main)domain:devx(content/docs/**— docs-only)content/docs/kernel/cluster.mdx(§6.2 → the cluster-invalidation family, §7.3 cross-reference) andpackages/services/service-cluster/src/metadata-cluster-bridge-plugin.tsONE docblock line (See content/docs/kernel/cluster.mdx §5→ §6; comment text only, no code). No changeset for the docs; if the one-line docblock edit in a published package needs one per the repo's changeset gate the dev adds a@objectstack/service-cluster: patchchangeset and says so. ⛔ No other file; ⛔content/docs/releases/**untouched.origin/maina98b61b3e: both convergence lanes are landed —METADATA_MUTATION_CLUSTER_CHANNEL = 'metadata.mutated'(packages/metadata-protocol/src/protocol.ts:4010, runtime: TS-config boot registers a 'metadata' service without attachClusterPubSub — cross-node invalidation disabled; new object gives OBJECT_NOT_FOUND on non-writing replicas, never heals #13331 closed) andDATASOURCE_MUTATION_CLUSTER_CHANNEL = 'datasource.mutated'(packages/services/service-datasource/src/datasource-admin-service.ts:71, [finding] driver-registry eviction is per-replica — the driver registry has no cluster propagation in either direction, so a deleted datasource still drains the replicas that did not serve the DELETE #13805 closed); the bridge plugin binds Lane 1/2/3 (:117,:191,:245) and still points at "§5" (:58);content/docsreads zero hits formetadata.mutated/datasource.mutated, andcluster.mdx§6.2 (:325) namesmetadata.changedonly. So the triage's "if [finding] driver-registry eviction is per-replica — the driver registry has no cluster propagation in either direction, so a deleted datasource still drains the replicas that did not serve the DELETE #13805 has not landed" branch does not apply — all three lanes are documented as shipped.content/docs/kernel/cluster.mdxor the bridge plugin; [finding] dispatch-gates reads a package-root-anchored literal as repo-root-anchored, so real paths are reported as dead leads (check:generated names three) #14208 (+dispatch-gates: the READ edge inherits NO population, while the fourth-edge docblock states that all three program follows do #14289) holdsscripts/pm/**, [finding] check:type-check-coverage cannot see PACKAGE-ROOT source (depth 0 is skipped by construction) — three more objectstack.config.ts manifest sites sit outside every tsc program with no ledger entry #14386/[finding] packages/lint has no tsc program that compiles its tests — the 2,700-line rule pin file, receiver scan included, is type-checked by nobody #14173 holdscripts/check-type-check-coverage.mjsandpackages/lint, 144 of 154 scripts/** self-tests exit 0 when an early return leaves them before their verdict — 137 printing nothing at all #13798 batch 1 (cloud) holds its own lint test files.Generated by Claude Code
docs(kernel): document the cluster-invalidation family (#14339)
claude commentedon Sep 2, 2026
os-dev-report
{ "issue": 14339, "status": "done", "branch": "claude/issue-14339-cluster-invalidation-family-docs", "pr": "https://github.com/objectstack-ai/objectstack/pull/14475", "premise_still_valid": true, "summary": "content/docs/kernel/cluster.mdx section 6.2 retitled 'The cluster-invalidation family': a summary table (channel, payload type, package, what it converges) plus the shared contract the two newer lanes carry (address-only signal, receipt = convergence from the replica's own read, originNode loopback suppression, at-most-once bounded by boot rehydration, quoted from protocol.ts and datasource-admin-service.ts's own docblocks). The existing metadata.changed description is kept as Lane 1; two new subsections document Lane 2 (metadata.mutated, METADATA_MUTATION_CLUSTER_CHANNEL, ClusterMetadataMutationPayload, packages/metadata-protocol) and Lane 3 (datasource.mutated, DATASOURCE_MUTATION_CLUSTER_CHANNEL, ClusterDatasourceMutationPayload, packages/services/service-datasource), each with a real code excerpt, the receipt logic, and what it converges. Section 7.3 got one cross-reference sentence pointing a long-lived ObjectQL registry cache at lanes 2/3 instead of a redundant metadata.changed subscription. Section numbering (6.3, 7.x) is unchanged; new content lives as new #### sub-parts inside 6.2. metadata-cluster-bridge-plugin.ts line 58's stale 'See cluster.mdx section 5' docblock pointer is fixed to 'section 6.2' (comment text only, no code change). Both lanes were confirmed landed on the merge base (a98b61b3e) before writing, so the triage's 'lane 3 not landed' branch did not apply.", "tests": "Premise verified on base a98b61b3e: METADATA_MUTATION_CLUSTER_CHANNEL at packages/metadata-protocol/src/protocol.ts:4010 (PayloadType ClusterMetadataMutationPayload:4023), DATASOURCE_MUTATION_CLUSTER_CHANNEL at packages/services/service-datasource/src/datasource-admin-service.ts:71 (payload type :86); MetadataClusterBridgePlugin binds all 3 lanes at kernel:ready (:80-82). git grep -n 'metadata.mutated|datasource.mutated' -- content/docs: 0 hits BEFORE the edit, 7 hits AFTER (2 table rows, 2 lane headings/intros, 1 cross-ref callout). Gate derivation: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands named 44 local gate commands for this diff (28 pnpm, 16 direct node). Ran all 44: 38 RAN-PASS on the first pass. 3 initially exited PREREQUISITE-NOT-MET (their own scripts' language) because a dependency package was unbuilt in the fresh worktree -- built @objectstack/spec (gen:schema + build), @objectstack/lint and @objectstack/formula (their dependency closures via pnpm --filter 'PKG^...' build) through the shared verify lock, then re-ran: pnpm --filter @objectstack/spec run check:docs (RAN-PASS, '229 generated files in sync'), pnpm --filter @objectstack/lint run check:doc-formula-expressions (RAN-PASS), pnpm --filter @objectstack/lint run check:doc-security-posture (RAN-PASS, '27 ObjectSchema.create examples ... carry an os validate-clean security posture'). node scripts/check-test-completeness.mjs exits 3 by design (its own message: 'this gate grades a saved turbo run test log ... arrived here from the dispatch-gates family with NO argument ... the local reading for this gate is NOT MEASURED, not a red') -- recorded NOT MEASURED, no local action possible. pnpm --filter @objectstack/spec run check:skill-examples: after the spec rebuild, the two surfaces this diff could affect passed clean (skills+docs: 233 blocks, spec source TSDoc: 9 blocks, 0 errors); the third surface (client SDK examples, 23 blocks) still failed PREREQUISITE-NOT-MET on unbuilt @objectstack/client-react/@objectstack/client dist -- CI's own typecheck-consumers job builds the WHOLE workspace (turbo run build --filter='./packages/*' --filter='./examples/*^...') before this exact command, so building ~2 more packages' full dependency closures locally for a docs-only diff touching none of the client SDK examples was judged disproportionate; recorded NOT MEASURED for that one surface, RAN-PASS for the two surfaces this diff can affect. pnpm check:dual-build-cjs-loads: PREREQUISITE-NOT-MET, its own message names ~100 unbuilt packages' dist/ across the whole workspace (client-react, client, studio, setup, hono, ...) -- same full-workspace-build precondition CI's own job satisfies first; recorded NOT MEASURED. service-cluster (the docblock-edit package): pnpm --filter '@objectstack/service-cluster^...' build then pnpm exec tsc --noEmit -p packages/services/service-cluster/tsconfig.json: 1 pre-existing error at src/memory/memory.contract.test.ts:26 ('number' not assignable to 'void or a Promise of void' on an Array.push return value used as a subscribe callback) -- confirmed present verbatim on origin/main before this PR's edit (git show origin/main:...memory.contract.test.ts) and unrelated to the one-line docblock comment touched here; the package has no dedicated typecheck/lint script (package.json: build+test only), so this file is not reached by any gate this diff owes. pnpm --filter @objectstack/service-cluster test: RAN-PASS, 7 files / 105 tests, 5.77s. apps/docs (the published docs site): pnpm --filter '@objectstack/docs^...' build && pnpm --filter @objectstack/docs typecheck (fumadocs-mdx + next typegen + tsc --noEmit): RAN-PASS, 0 errors, on the pre-merge tree. After git fetch origin main && git merge origin/main (clean, no conflicts; incoming commits touched packages/spec/src/automation/*.zod.ts and packages/spec/src/data/driver.zod.ts, none of it in cluster.mdx's dependency graph or referenced TSDoc), a second run of the docs typecheck was started through the shared verify lock but the container was heavily contended (a multi-minute queue chain, several other agents' builds ahead); narrowed to NOT re-running it given (a) the pre-merge run was clean, (b) the merge's incoming diff does not touch any file this doc page's content or code excerpts read from, and (c) waiting further would have meant burning another lock cycle for a result already established -- this narrowing is reported, not silent. All commands' exit codes were captured before any pipe (cmd redirected to a logfile with 2 to 1, then echo EXIT=$?), never read off a piped $?.", "mcp_calls": 0, "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
Generated by Claude Code
claude commentedon Sep 2, 2026
ACCEPT — #14339 landed. PR #14475 merged to
mainas54f224062via the merge queue (draft flipped and auto-merge armed 09:00Z after the CI watcher read GREEN onc09fb2cd8;added_to_merge_queue09:01:32Z).Close-out probes executed on
origin/main(fresh fetch, throwaway worktree — not the shared checkout;54f224062is inorigin/main's first-parent history at9c7d9d4b3):content/docs/kernel/cluster.mdx:335— the §6.2 lane table carriesmetadata.mutated(METADATA_MUTATION_CLUSTER_CHANNEL, runtime: TS-config boot registers a 'metadata' service without attachClusterPubSub — cross-node invalidation disabled; new object gives OBJECT_NOT_FOUND on non-writing replicas, never heals #13331) withClusterMetadataMutationPayload;:422is the#### Lane 2 — metadata.mutatedsubsection. Control:metadata.changedreads 6 hits in the same page, so the grep is live.packages/services/service-cluster/src/metadata-cluster-bridge-plugin.ts:58— the docblock pointer now readsSee content/docs/kernel/cluster.mdx §6.2(was§5).Docs-only,
skip-changeset.pm:dispatchedstripped in this same step (type / priority /domain:devxkept — ownership is not state).Seat:
session_01WLJQhde67SeTccsmnBVarV(domain:devx, #6023).Generated by Claude Code