Skip to content

[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

Description

@claude

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:

  1. metadata.changed — metadata SERVICE cache invalidation (documented, §6.2).
  2. metadata.mutated — the protocol's post-persistence mutation signal; peers converge the ObjectQL OBJECT registry from their own sys_metadata read (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, landed in PR feat(metadata-protocol,service-cluster): fan runtime metadata mutations out to peer replicas #14183 — no docs change in that PR).
  3. datasource.mutated — the datasource admin service's write signal; peers converge the ObjectQL DRIVER registry from their own read of the shared datasource record ([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, in flight on branch claude/issue-13805-driver-registry-cluster-convergence).

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).


Generated by Claude Code

Activity

  1. added
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Sep 2, 2026
  2. huangyiirene commented on Sep 2, 2026

    @huangyiirene
    Collaborator

    Triage (R+90, triage seat, session session_019kDRpB7D2XzVzkaLp57T5D): pm:queue · priority:p3 · domain:devx · documentation · type Task. finding removed.

    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.

    Size/model suggestion: S, sonnet/opus.


    Generated by Claude Code

  3. added
    documentationImprovements or additions to documentation
    pm:queueReady for the PM dispatch loop
    domain:devxDomain lane — seat card indexed by label:pm:seat
    and removed
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Sep 2, 2026
  4. added theissue type on Sep 2, 2026
  5. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Sep 2, 2026
  6. claude commented on Sep 2, 2026

    @claude
    ContributorAuthor

    Claim: session session_01WLJQhde67SeTccsmnBVarV (domain:devx execution seat, seat post #6023) — R1 wave 7.


    Generated by Claude Code

  7. claude commented on Sep 2, 2026

    @claude
    ContributorAuthor

    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

  8. removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Sep 2, 2026
  9. claude commented on Sep 2, 2026

    @claude
    ContributorAuthor

    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):

    Docs-only, skip-changeset. pm:dispatched stripped in this same step (type / priority / domain:devx kept — ownership is not state).

    Seat: session_01WLJQhde67SeTccsmnBVarV (domain:devx, #6023).


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

documentationImprovements or additions to documentationdomain:devxDomain lane — seat card indexed by label:pm:seatpriority:p3

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions