Skip to content

fix(service-automation): a connector credential written through the door reaches every replica - #27941

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-m511py
Oct 30, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-m511py

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Elon · project thread
Fixes #27428

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:多副本部署里,任一副本通过凭据门写入或清除连接器凭据后,发一条仅含地址的集群广播(connector-credential.mutated,载荷 { originNode, ref });其它副本收到后跑自己的连接器对账,重读共享凭据行,被挂起的连接器随即恢复。
  • 为什么改:此前只有接请求的那个副本恢复,其它副本上被上游拒过的连接器一直挂着,直到元数据重载或重启(裁决 6513279565,字母 A 挂 A1)。
  • 风险与回滚:只加一条内部通道,单副本或内存集群驱动下不挂载、行为不变;至多一次投递,丢失由下次重载或重启兜底。回滚即撤本 PR。
  • 先看:packages/services/service-automation/src/plugin.ts(门的发布与收方对账)、packages/services/service-cluster/src/metadata-cluster-bridge-plugin.ts(第四条通道)、packages/services/service-automation/src/connector-credential-cluster.test.ts(双内核 pin)。
  • 两分钟验证:pnpm --filter @objectstack/service-automation exec vitest run src/connector-credential-cluster.test.ts,预期 5 个用例全绿。
  • 需要你做的:无,席位落地。

What changes

  • The publish. The connector credential door (PUT|DELETE /api/v1/automation/connectors/credential-refs/:ref) publishes { originNode, ref } on connector-credential.mutated right after store.set succeeds and after store.clear clears a row, then reconciles its own replica as before. Address only: no value, fingerprint or revision rides the channel. A refused write (400 / 403 / 404 / 409 / 503) publishes nothing. Best-effort and fire-and-forget, like lanes 2 and 3: a publish failure is logged at warn and never fails the write.
  • The receiver. A peer drops its own originNode, then runs reconcileDeclaredConnectors on its reconcile queue (materializeDeclaredConnectors(ctx, { fatal: false })), which re-reads the shared sys_connector_credential rows. The row's revision joins each instance's signature, so exactly the instances naming the changed reference re-materialize, held ones included; a cleared reference degrades them, naming it. It logs one info line per peer write it follows.
  • The seam. AutomationEngine.attachConnectorCredentialPubSub(pubsub, nodeId) delegates to the plugin's credential door (ConnectorCredentialDoor.attachClusterPubSub), idempotent on (pubsub, nodeId) and detached at destroy(). The channel name and payload type live in connector-credentials.ts; nothing new is exported from the package root.
  • Lane 4 in MetadataClusterBridgePlugin. Duck-typed against the automation service's attachConnectorCredentialPubSub() beside lanes 1 to 3, skipped on the in-process memory driver, its own attach error and detach on kernel:shutdown.
  • Docs. content/docs/kernel/cluster.mdx §6.2 gains the lane 4 row; the family's prose now says four lanes and names the credential rows as lane 4's source of truth.
  • Changeset: patch for @objectstack/service-automation and @objectstack/service-cluster.

Why

Ruling 6513279565 on #27428 (letter A, attached at A1): one new address-only lane of the §6.2 family, published by the credential door, received as a reconcile that re-reads the shared row, enumerated in the bridge with the other three lanes. Not a synthetic metadata.mutated, no polling, no self-attach inside service-automation.

Tests

  • packages/services/service-automation/src/connector-credential-cluster.test.ts (new): two AutomationServicePlugin kernels over ONE SQLite database and one shared bus that delivers to every subscriber, publisher included. Each replica is attached through attachConnectorCredentialPubSub(), the seam the bridge calls.
    • a PUT on A re-materializes the instance B holds (its stored value was refused upstream, so no retry timer) with the new value, with no reload on B;
    • a DELETE on A degrades B's live instance, naming the reference;
    • control: B with no transport stays held after A's PUT, the pre-fix shape;
    • the payload is exactly { originNode, ref } with partition key connector-credential:REF, after a set and after a clear, and a refused write publishes nothing;
    • A's own publish is dropped on A and followed on B, and a write on B reaches A.
  • packages/services/service-cluster/src/metadata-cluster-bridge-plugin.test.ts: six lane-4 cases (attach on a cross-process driver with the cluster's pubsub and node id, all four lanes together, skip on memory, quiet skip with no service or no seam, a throwing attach that leaves lanes 1 to 3 up, detach on shutdown when the lane-3 detach throws).

Acceptance notes

Verification

Head f78a7f75.

Red on the base, ablations, restore
  • The new test file against origin/main 8c7021df (the three source files restored from the base): 5/5 red, TypeError: engine.attachConnectorCredentialPubSub is not a function.
  • Through scripts/ablation-replace.mjs (anchor hit 1 → 0, restored blob == HEAD, git diff HEAD empty after each):
    • the set door's publish removed: 4 red (the PUT, DELETE, payload and loopback cases), the control green;
    • the clear door's publish removed: 2 red (the DELETE and payload cases), 3 green;
    • the loopback guard removed: 1 red (the loopback case), 4 green.
Package suites and gates
  • pnpm --filter @objectstack/service-automation exec vitest run --maxWorkers=2: 219 files, 2808/2808 passed.
  • pnpm --filter @objectstack/service-cluster test: 7 files, 111/111 passed; typecheck exit 0 for both packages.
  • node scripts/pm/dispatch-gates.mjs --commands: 92 derived families, all run, --ran reconciliation "92 derived, 92 run, 0 NOT-MEASURED, 0 UNRUN". 89 exited 0 on the first pass; three first answered exit 3 (prerequisite) and exited 0 once it was met: check-plugin-teardown-shape --self-test (pinned commit fetched into the shallow clone), spec check:skill-examples and check:dual-build-cjs-loads (after a whole-tree turbo run build).
  • pnpm lint: exit 0.

Generated by Claude Code

…-m511py

# Conflicts:
#	packages/services/service-automation/src/plugin.ts
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Oct 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/service-automation, @objectstack/service-cluster, touching 18 documentable anchor(s).

5 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/automation/connectors.mdx (via /api/v1/automation/connectors/credential-refs/:ref (route, a path literal in a comment on a changed line))
  • content/docs/automation/flows.mdx (via AutomationServicePlugin (symbol, a top-level class))
  • content/docs/concepts/metadata-lifecycle.mdx (via MetadataClusterBridgePlugin (symbol, a top-level class), originNode (symbol, a field of interface ClusterConnectorCredentialMutationPayload))
  • content/docs/kernel/cluster.mdx (via CONNECTOR_CREDENTIAL_MUTATION_CLUSTER_CHANNEL (symbol, a top-level const object), ClusterConnectorCredentialMutationPayload (symbol, a top-level interface; a top-level type), MetadataClusterBridgePlugin (symbol, a top-level class), originNode (symbol, a field of interface ClusterConnectorCredentialMutationPayload), /api/v1/automation/connectors/credential-refs/:ref (route, a path literal in a comment on a changed line))
  • content/docs/kernel/services-checklist.mdx (via MetadataClusterBridgePlugin (symbol, a top-level class), attachClusterPubSub (symbol, a method of interface ConnectorCredentialDoor))

⛔ 3 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v16.mdx (via AutomationEngine (symbol, a top-level class))
  • content/docs/releases/v17/17-0.mdx (via AutomationEngine (symbol, a top-level class))
  • content/docs/releases/v17/17-6.mdx (via AutomationEngine (symbol, a top-level class), AutomationServicePlugin (symbol, a top-level class))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 58 of 210 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 10 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json ed0e71db94a73acc02512ff2ee0655106d02e585 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 57a7b229611cee9904d297a226cc34d318dfa18b — the merge of head 8b40e4fa254306912fb197fe434a91157d9c7937 into base ed0e71db94a73acc02512ff2ee0655106d02e585, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 57a7b229611cee9904d297a226cc34d318dfa18b && git checkout 57a7b229611cee9904d297a226cc34d318dfa18b
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin ed0e71db94a73acc02512ff2ee0655106d02e585 8b40e4fa254306912fb197fe434a91157d9c7937 && git checkout -B drift-repro ed0e71db94a73acc02512ff2ee0655106d02e585 && git merge --no-ff 8b40e4fa254306912fb197fe434a91157d9c7937

node scripts/docs-audit/affected-docs.mjs --json ed0e71db94a73acc02512ff2ee0655106d02e585

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs ed0e71db94a73acc02512ff2ee0655106d02e585 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-elon-musk · session_016WV4czuTWNgKR22fKv7jia

Seat ACCEPT · PR #27941 · head 8b40e4fa254306912fb197fe434a91157d9c7937 · 2026-10-30T05:05Z

维护者速读

When the credential door (PUT|DELETE /api/v1/automation/connectors/credential-refs/:ref) writes or clears a connector credential on one replica, the replica now publishes an address-only message on the cluster lane connector-credential.mutated. The payload is { originNode, ref } and never carries a value. Every other replica then reconciles its declared connectors on its reconcile queue and re-reads the shared row. A connector that was held on a peer recovers without a reload. Before this, only the replica that took the request recovered. This implements ruling 6513279565 (letter A, attached at A1): the fourth duck-typed lane in MetadataClusterBridgePlugin, with a row in cluster.mdx §6.2. There is no synthetic metadata.mutated and no polling. Single-replica and memory-driver deployments are unchanged.

席位核对

  • Shape: draft → main, Fixes #27428.
    • Eight files across service-automation (door publish, receiver, channel constant, engine seam), service-cluster (lane 4), the §6.2 row, tests and a changeset.
    • check-governed-merges --pr 27941: NOT governed, +672 / −17.
    • File surface as claimed.
  • Read of the diff:
    • The publish happens only after a successful store.set or store.clear, as a best-effort warn on failure.
    • The receiver drops its own originNode and ignores an empty ref.
    • The receiver runs materializeDeclaredConnectors, which is the serialized onReconcileQueue wrapper around reconcileDeclaredConnectors.
    • Attach is idempotent on (pubsub, nodeId) and is detached at destroy().
    • Nothing new is exported from the package root.
  • Seat ruling on the open question: Clause-②: no stands. AutomationEngine.attachConnectorCredentialPubSub is an internal duck-typed seam between two server packages, the same pattern as attachDatasourceMutationPubSub and attachAuthzInvalidationPubSub. It widens no published accept set, so no contract review is owed. Metadata-protocol: none, patch on both packages.
  • Tests:
    • A two-kernel pin over one SQLite store and one shared bus was red on the base, 5 of 5. Three ablations each go red where declared.
    • Six bridge lane-4 cases cover the memory skip, a throwing attach that leaves lanes 1 to 3 up, and detach on shutdown.
    • service-automation 2808/2808 and service-cluster 111/111 (os-dev-report 6515174220).
  • Serial: draft feat(service-datasource): a datasource naming a connector instance in external.connector borrows its credential, and its driver presents it per request (ADR-0015 D7) #27819 also edits service-automation/src/plugin.ts. This head already merged main, and whichever of the two lands later merges main again.
  • Out of scope, per the ruling: whether a published connector change materialises only on the publishing replica. It gets its own card once measured.
  • Gate check-runs on this head: 50 total, 38 success, 12 skipped by the path filter, 0 failed. mergeable_state: clean.
  • Landing: Tier S, under the maintainer's landing grant for this term (2026-10-29 22:05Z, seat thread). Ready + auto-merge through the relay.

ACCEPT

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

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

1 participant