Skip to content

fix(service-automation): a dispatch to a held connector instance names its fix, not a retry - #28037

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-0vmpzv
Oct 30, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-0vmpzv

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Elon · project thread

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

Fixes #28016

Clause-②: no
Metadata-protocol: none
Skills-docs: none — git grep -n -i -e "retries automatically" -e "retried with backoff" -- skills finds nothing, and git grep -n -i degraded -- skills finds one line (skills/objectstack-automation/references/integration-recipes.md:9, an unset ref leaves its instance degraded until set), which stays true. The docs row this card owes is in this PR. docs/adr/0097-declarative-connector-instances.md:190 still quotes "the platform retries automatically" for every husk; it is governed and outside this card's surface (see Acceptance notes).

维护者速读

What changes

  • The degraded record carries the hold. RegisteredConnector gains an optional degradedHold: ConnectorHold, { kind: 'declaration' } or { kind: 'credential', ref }. registerDegradedConnector takes it as an optional fifth argument (engine delegate and the body in plugin-extension-api.ts), and AutomationEngine.getConnectorDegradedHold(name) reads it. A later degrade of the same name replaces the record, so an instance that goes back on the backoff loses the hold.
  • plugin.ts passes it at the two hold sites. failDegradedConnectorInstance (a configuration fault met on a retry, service-automation: a configuration fault first seen on a degraded connector's backoff retry is logged and skipped, but the instance stays degraded with the stale "could not reach" reason and keeps retrying every 5 minutes #27992) passes declaration. holdConnectorForRejectedCredential passes credential with the row's ref for a stored value, and declaration for an instance with no credentialRef (its credential is written into the declaration, so only an edit changes what it sends). An environment or vault value stays on the backoff and passes nothing.
  • The refusal (builtin/connector-nodes.ts). Held: Dispatch is unavailable until its declaration is edited (or the server restarts); the platform does not retry it. or … until credential '<ref>' is set again (or the server restarts); …. On the backoff: unchanged, … the platform retries automatically. A reason that already ends with a full stop no longer gets a second one (…does not list.. Dispatch before).
  • The engine's Connector registered DEGRADED warn says the materializer does not retry a held instance, and until what. The unheld wording is byte-identical (pinned in degraded-register-cause.test.ts).
  • Docs. content/docs/automation/connectors.mdx's failure-modes table gains the row for a configuration fault met on a retry.
  • Changeset. @objectstack/service-automation patch.

Tests

  • New src/connector-held-dispatch-refusal.test.ts (4): a configuration fault met on the backoff retry; a credential written into the declaration refused by the upstream; control: an unreachable upstream keeps "retries automatically"; the engine record carries the hold, its warn says so, and an unheld degrade clears it.
  • src/connector-credential-door.test.ts (+3, real ObjectQL over SQLite): a stored value refused at materialization (mcp shape) and on a call (rest / openapi shape) says to set credential <ref> again; control: an environment value refused on a call keeps "retries automatically".

Serial: open PR #27951 (engine.ts)

#27951 (seat 4) is still open. The two engine.ts regions do not overlap. This PR's hunks: @@ -667,8 +667,27 @@ (RegisteredConnector), @@ -3982,12 +4001,16 @@ and @@ -3995,7 +4018,7 @@ (the registerDegradedConnector delegate), @@ -4142,6 +4165,17 @@ (the new reader beside getConnectorDegradedReason). #27951's hunks against main: @@ -6373,6 …, @@ -6398,8 …, @@ -6416,7 …, @@ -6594,38 … (resume paths). Over 2,000 lines apart.

Acceptance notes

  • docs/adr/0097-declarative-connector-instances.md:190 says a dispatch to a husk fails with "the platform retries automatically". After this PR that holds only for an instance on the backoff. The ADR is governed and outside this card's surface, so it is left as is.
  • ConnectorDescriptor (GET /api/v1/automation/connectors) does not carry the hold; adding it is a packages/spec change, outside this card. The listing's degradedReason for a refused credential already says it is not retried.
  • The additions to the engine's surface (getConnectorDegradedHold, the optional degradedHold field and fifth argument, the ConnectorHold type exported from engine.ts, not from the package entry) are additive and optional; no accept set narrows, hence Clause-②: no as ordered.

Verification

All readings at head 824e0f5d.

Red on main, green with the fix, ablations, restore
  • On main's engine.ts, plugin-extension-api.ts, plugin.ts, builtin/connector-nodes.ts with the new tests: Tests 4 failed | 25 passed (29). Each held case received … Dispatch is unavailable until that is resolved; the platform retries automatically. (the configuration case as …does not list.. Dispatch …). Both controls passed.
  • With the fix: the five related files (connector-held-dispatch-refusal, connector-credential-door, connector-retry-configuration-fault, connector-materialization, degraded-register-cause) Tests 93 passed (93).
  • Ablation through scripts/ablation-replace.mjs, fix committed first, each restored with blob == HEAD and git diff HEAD empty:
    1. builtin/connector-nodes.ts, const hold = engine.getConnectorDegradedHold(cfg.connectorId); to undefined: anchor 1 to 0, blob 2491245ee725 to 98ae59d65b55, Tests 4 failed | 26 passed (30) (the four held dispatch cases; controls green).
    2. plugin.ts, the credential-hold argument to undefined: blob 6a0a751a1469 to 11d034a84acc, Tests 3 failed | 27 passed (30) (declared credential, stored credential x2).
    3. plugin.ts, failDegradedConnectorInstance's { kind: 'declaration' } to undefined: Tests 1 failed | 3 passed (4) (the configuration case).
Package suite, typecheck, lint
  • pnpm --filter @objectstack/service-automation exec vitest run: Test Files 224 passed (224), Tests 2848 passed | 1 expected fail (2849).
  • pnpm --filter @objectstack/service-automation run typecheck: exit 0.
  • pnpm lint: exit 0.
dispatch-gates families (92 derived, 92 run, 0 NOT-MEASURED)

check-plugin-teardown-shape --self-test after fetching its pinned commit; check:skill-examples and check:dual-build-cjs-loads exited 3 on the closure build and 0 after a whole-tree turbo run build (exit 0). dispatch-gates --ran: 92 derived families accounted for, 92 run, 0 NOT-MEASURED.

node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-adr-links.mjs :: exit 0
node scripts/check-adr-links.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-adoption.mjs :: exit 0
node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-doc-route-spelling.mjs --advisory :: exit 0
node scripts/check-doc-route-spelling.mjs --self-test :: exit 0
node scripts/check-docs-section-name.mjs :: exit 0
node scripts/check-docs-section-name.mjs --self-test :: exit 0
node scripts/check-dts-emitted.mjs --self-test :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-issue-citations.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0
node scripts/check-section-landing-index.mjs :: exit 0
node scripts/check-section-landing-index.mjs --self-test :: exit 0
node scripts/check-tenant-audit-census.mjs :: exit 0
node scripts/check-tenant-audit-census.mjs --self-test :: exit 0
node scripts/ci/assert-turbo-remote-cache.mjs --self-test :: exit 0
node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0
node scripts/ci/prune-turbo-cache.mjs --self-test :: exit 0
node scripts/docs-audit/check-affected-docs.mjs :: exit 0
node scripts/docs-audit/check-drift-comment.mjs :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0
pnpm --filter @objectstack/lint run check:doc-security-posture :: exit 0
pnpm --filter @objectstack/spec run check:docs :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm --filter @objectstack/spec run check:empty-state :: exit 0
pnpm --filter @objectstack/spec run check:liveness :: exit 0
pnpm --filter @objectstack/spec run check:strictness-ledger :: exit 0
pnpm --filter @objectstack/spec run check:variant-docs :: exit 0
pnpm --filter @objectstack/spec run check:yaml-examples :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:ci-filter-parity :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:corpus-claim-drift :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:dispatcher-error-vocabulary :: exit 0
pnpm check:doc-anchors :: exit 0
pnpm check:doc-authoring :: exit 0
pnpm check:doc-frontmatter :: exit 0
pnpm check:docs-audit-scope :: exit 0
pnpm check:docs-redirects :: exit 0
pnpm check:docs-single-h1 :: exit 0
pnpm check:docs-spec-enumerations :: exit 0
pnpm check:docs-transcript-drift :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:dts-closure :: exit 0
pnpm check:durability-log-level :: exit 0
pnpm check:engine-double-contract :: exit 0
pnpm check:error-status-conformance :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:issue-citations :: exit 0
pnpm check:keyed-text-bounds :: exit 0
pnpm check:lean-entry-closure :: exit 0
pnpm check:logger-receiver-detach :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:objectql-double-limit :: exit 0
pnpm check:org-identifier :: exit 0
pnpm check:page-declaration-shape :: exit 0
pnpm check:platform-object-tenancy-census :: exit 0
pnpm check:pm-changeset-deadline-census :: exit 0
pnpm check:published-files :: exit 0
pnpm check:published-readme-links :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:react-page-adapter-contract :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:registry-log-declared :: exit 0
pnpm check:rest-log-spy-declared :: exit 0
pnpm check:role-word :: exit 0
pnpm check:skill-identifier-liveness :: exit 0
pnpm check:slot-lookup :: exit 0
pnpm check:sourcemap-no-sources-content :: exit 0
pnpm check:system-context-census :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:vendor-version-stamps :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0
pnpm --filter @objectstack/spec run check:skill-examples :: exit 0
pnpm check:dual-build-cjs-loads :: exit 0
pnpm lint :: exit 0

Generated by Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_016WV4cw8ZC8W9Jeq9E7gY3X

…s its fix, not a retry

A degraded connector instance the platform does not retry (a configuration
fault met on a retry, or a credential the upstream refused that only a new
value or an edit of the declaration changes) told a connector_action
dispatch "the platform retries automatically". The engine's degraded record
now carries the hold, and the refusal names what re-attempts it: edit the
declaration, or set the stored credential again. An instance on the backoff
keeps the retry sentence. The connectors page gains a row for a
configuration fault met on a retry.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4cw8ZC8W9Jeq9E7gY3X
@github-actions github-actions Bot added size/m 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 1 package(s): @objectstack/service-automation, touching 10 documentable anchor(s).

⛔ 3 release-owned page(s) 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))

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
  • 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 e1dde1a5c15566bcc24d6c641b53d8282fcf655b → packageMentionDocs.

Which tree this was computed on

This run read content/docs from d5e5e482cf5335a1be4044826217b56e3551f792 — the merge of head 824e0f5d9021a0ee063dd1b133235c433fd089e9 into base e1dde1a5c15566bcc24d6c641b53d8282fcf655b, 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 d5e5e482cf5335a1be4044826217b56e3551f792 && git checkout d5e5e482cf5335a1be4044826217b56e3551f792
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e1dde1a5c15566bcc24d6c641b53d8282fcf655b 824e0f5d9021a0ee063dd1b133235c433fd089e9 && git checkout -B drift-repro e1dde1a5c15566bcc24d6c641b53d8282fcf655b && git merge --no-ff 824e0f5d9021a0ee063dd1b133235c433fd089e9

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

⚠️ 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 e1dde1a5c15566bcc24d6c641b53d8282fcf655b → 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 #28037 · head 824e0f5d9021a0ee063dd1b133235c433fd089e9 · 2026-10-30T10:18Z

维护者速读

A connector_action dispatch to a degraded connector used to end with "the platform retries automatically", even for an instance the platform does not retry. Now a held instance names its fix. A configuration fault met on a retry (#27992), or a credential written into the declaration that the upstream refused, gets "until its declaration is edited". A stored credential the upstream refused (#27237) gets "until credential '' is set again". Only an instance on the backoff keeps "retries automatically". The connectors page gains the failure-modes row for a configuration fault met on a retry.

席位核对

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/m tests tooling

Projects

None yet

1 participant