Skip to content

fix(platform-objects)!: retire the sys_account link_social action, dead on every boot; unlink_account stays - #21894

Merged
objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/issue-21849-retire-link-social
Oct 5, 2026
Merged

objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/issue-21849-retire-link-social

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #21849
Clause-②: no (narrowing)

Retires the sys_account link_social action from @objectstack/platform-objects under ADR-0049 enforce-or-remove, as ruled on the card (ruling D, comment 5995717941; maintainer reply verbatim: 「同意」). unlink_account stays. Self-service linking returns as a native console surface reading /auth/config once a linking need is named; that is not this PR.

Why

link_social was a type: 'url' toolbar action. It navigated to a GET of better-auth's social sign-in route, which better-auth serves as POST only, and it offered a fixed list of seven providers whatever the boot had configured, with no visibility gate. It was dead on every boot: the earlier measurement on the card (5992085207) read 404 on a provider-less boot and on a configured one. The ruled direction (options from the configured providers, hidden when none) cannot be said in today's action contract, so the ruling retires the action instead of widening four lanes for one consumer.

Linking stays reachable through the signed-in POST /api/v1/auth/link-social, which is auth.accounts.linkSocial in @objectstack/client. That door, its plugin-auth route-ledger rows and the SDK method are untouched.

What changed

  • packages/platform-objects/src/identity/sys-account.object.ts: the action and the comment paragraph that introduced it are gone. A short comment now records why there is no link action and where linking lives. unlink_account keeps its name, type, target, mode, placement and row-id param. Its confirm question drops "from their account settings"; see Deviation from the claim below.
  • Translations, regenerated with the repo's tool (node scripts/check-i18n-bundles.mjs --write --filter=platform-objects). The diff removes only the sys_account._actions.link_social block from each of the four objects bundles (17 lines each) and its seven provenance rows from each of the three source-hash tables, and rewrites the en unlink question from source. The three translated unlink questions are hand-written values, edited by hand to match.
  • The three echo-decision ledgers drop their seven link_social provider-brand rows and the now-unused brand reason. The size pins move with them: zh-CN 45 to 38 rows (42 to 35 echoes), ja-JP 46 to 39 (43 to 36), es-ES 57 to 50 (54 to 47). A header note in each says why.
  • New pin packages/platform-objects/src/identity/sys-account-link-social-retired.test.ts.
  • Comments that cited the action: sys-member.object.ts (the add_member icon note now cites the Account app's Linked Accounts entry, which uses the same icon) and apps/account.app.ts (the resultDialog list).
  • Outside the lane, declared on the claim:
    • packages/spec/src/ui/action.zod.ts: the target docblock sentence that cited link_social and its dead GET target is removed. Docblock only, no schema line; the interpolation and encoding sentences stay.
    • content/docs/protocol/objectui/actions.mdx: the URL Actions example no longer teaches link_social. It is replaced by a working ${param.X} example (a Maps search URL with an address param); the interpolation prose stays.
  • .changeset/21849-retire-sys-account-link-social.md: @objectstack/platform-objects: minor, BREAKING, Clause-②: no (narrowing), ADR-0087 not-required (no-migration-prescription). That category fits because the withdrawn action is platform-shipped metadata on a lock: 'full' object: no spec key, spelling, export name or config field is retired, nothing an author wrote needs rewriting, and no stored row can carry it. No @objectstack/spec entry: its change is one docblock sentence, and no gate asked for one.

The checklist half left this PR

The domain:devx seat objected on the card (5996996202): identity-auth.linked-accounts-social in docs/qa/platform-checklist/areas/identity-auth.json is held by the claim on #21851, whose entry adds the item's link fixture through POST /api/v1/auth/link-social. #21851 had not landed when this PR opened, so the checklist half is not here. The file is restored to origin/main byte for byte: git diff origin/main -- docs/qa/platform-checklist/areas/identity-auth.json is empty, and the blob at HEAD equals the one on origin/main and at the base (f0734d3cb7). The seat files that revision as its own card when this lands, to be worked once #21851 has landed. Until then the item's link step still names the retired action.

Deviation from the claim

The claim said unlink_account stays byte-identical. Its confirm question told the user they could re-link "from their account settings", and after this retirement no console surface offers a link (zero hits for a link affordance in objectui at the pin, see H4). The dev contract says a shipped text that this change makes false is fixed in the same change, so the clause is dropped in all four locales. Name, type, target, mode, placement and params are unchanged and pinned. Reverting that one sentence is a single-file edit plus a regeneration, if the seat prefers the claim's reading.

Census

H1, every reference (base e864db56df, outside CHANGELOG.md).

Reference Disposition
sys-account.object.ts: the action (about :58-84) and its intro comment (:51-56) removed; a new comment names the retired action as a record
sys-member.object.ts :137 reworded
apps/account.app.ts :22 removed from the resultDialog list
four *.objects.generated.ts regenerated, leaves removed
three *.source-hashes.generated.ts regenerated, 7 rows each removed
three objects-*-echo-decisions.test.ts 7 rows each removed, counts moved; one header note each names the retirement
packages/spec/src/ui/action.zod.ts :1035-1036 sentence removed (docblock only)
content/docs/protocol/objectui/actions.mdx :97-100 example replaced
docs/qa/platform-checklist/areas/identity-auth.json (7 lines) not touched here (see above)
packages/spec/src/ui/inline-action.test.ts :307 (the sign-in URL as a parse-acceptance input) stays: an incidental fixture, outside the docblock-only scope
plugin-auth route-ledger rows for POST /api/v1/auth/link-social, packages/client auth.accounts.linkSocial stay: the door is not retired

H2, translations. The regenerated diff is 90 removed lines and 1 added: 4 times 17 lines of the link_social block, 3 times 7 provenance rows, and the one en unlink question rewritten from source. No other key moved. The echo ledgers drop exactly the seven provider rows each, and pnpm check:i18n and pnpm check:i18n-stale-fill are green (below).

H3, declaration reach, measured on built dist/**/*.d.ts:

  • With the action present (the base shape, rebuilt in the reverse-verification leg), link_social appears in 2 declaration files, dist/identity/index.d.ts and .d.mts, once each. It sits inside the type argument of SysAccount's declared type, which is ObjectSchema.create's return type: Omit of ServiceObject without fields, intersected with a Pick of the literal that keeps only fields. So no reachable member carries it. A tsc probe on that build compiled const probe: ActionName = 'zzz_not_an_action', where ActionName is the type of SysAccount.actions[number].name, which therefore resolves to string. The control line in the same file, a non-field assigned to keyof (typeof SysAccount)['fields'], failed with TS2322 as expected.
  • The translation bundles: 0 declaration hits even while the leaves were in the JS. The bundles are annotated NonNullable of TranslationData['objects'], so no key types reach ./apps or ./metadata-translations.
  • At HEAD: 0 declaration files and 0 JS files carry the action. The comment that names it survives into 4 JS files.

So the exported types are structurally unchanged and the narrowing is runtime and wire only. The Clause-②: no (narrowing) arm stands as declared.

H4, consumers. No workspace code, test, example app or dogfood test reads the action by name outside the H1 rows. objectui at the pinned .objectui-sha 0abd4f9f87, from a depth-1 fetch of that commit: git grep -e link_social -e 'Link Social' -e linkSocial -e link-social gives 0 hits (exit 1). The control git grep sys_account on the same tree gives 4 hits (CHANGELOGs), so the grep reached the tree. objectui's own ActionRunner tests use the sign-in URL as a generic url-action fixture and import nothing from here. No objectui change and no pin bump are needed (Post-Task Checklist item 4).

H5, stored data. None. The action is code-shipped metadata registered at boot, never a row. sys_account is protection.lock: 'full', so evaluateLockForWrite refuses every overlay save with ITEM_LOCKED, and no sys_metadata overlay can carry the action. No migration, seed, example or dogfood fixture names it.

Tests

  • New pin, 11 cases: pnpm --filter @objectstack/platform-objects exec vitest run --maxWorkers=2 src/identity/sys-account-link-social-retired.test.ts reads Tests 11 passed (11).
  • Package: pnpm --filter @objectstack/platform-objects test reads Test Files 60 passed (60) · Tests 960 passed (960). pnpm --filter @objectstack/platform-objects typecheck exits 0; check:test-typecheck puts the new pin and the edited ledgers in its program (--listFilesOnly).
  • Consumer: pnpm --filter @objectstack/runtime exec vitest run --maxWorkers=2 src/action-execution-destructive.test.ts reads Tests 66 passed (66); it reads the identity actions off their real declarations.
  • pnpm --filter @objectstack/spec check:generated: all 15 generated artifacts up to date against the rebuilt spec dist.

Reverse verification (on committed HEAD a299763014, through scripts/ablation-replace.mjs, which restores on exit, INT and TERM):

  • Leg A, the action restored in source. The anchor hit once and the blob moved 55baacb20f to 1faf2af6f8. pnpm --filter @objectstack/platform-objects build ran, and ablation-dist-preflight found the marker in 6 built files. The pin went red: Tests 2 failed, 9 passed (11), on "declares exactly one action" and "no action targets a social sign-in or link door". Restored: the blob equals HEAD and git diff HEAD is empty. Rebuilt, preflight --absent read the marker absent from all 66 built files with a clean tree, and the pin is green again.
  • Leg B, a link_social leaf put back in the en bundle. The anchor hit once and the blob moved 0d214ad760 to 3bddbb31f8. The pin went red: Tests 1 failed, 10 passed (11), on "en: sys_account._actions holds unlink_account and no link_social". Restored the same way: blob equality and an empty git diff HEAD. No build was needed for this leg, because the pin imports the bundle by relative source path, not through exports.

Whole-repo pin sweep. Pins asserting sys_account's action set, count or link_social keys were swept repo-wide. They are the three echo ledgers (re-pinned on their new row counts, which assert the substance: 38, 39 and 50 rows) plus platform-objects.test.ts, action-confirm-one-dialog.test.ts, confirm-question-carryover.test.ts, action-predicate-sparse-face.test.ts and runtime's action-execution-destructive.test.ts, which read unlink_account or iterate the object's actions and need no change. The new pin asserts the action set itself (['unlink_account']), not only that a name is gone.

Gates

Taken at HEAD c2bea2c789, after the last merge of origin/main.

  • Derived. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, with no paths, derived 109 commands, and all 109 were run. --ran reconciles them: ✓ dispatch-gates --ran: 109 derived famil(ies) accounted for — 109 run, 0 NOT-MEASURED.
  • A first-pass refusal. Five @objectstack/spec gates refused with PREREQUISITE NOT MET (exit 3), because the merge had moved spec test files and the spec dist input digest no longer matched. After pnpm --filter @objectstack/spec build, the whole spec family (22 commands) was rerun, and all of it is green.
  • The artifact-roster block the derivation prints outside its total: 54 commands, 52 at exit 0. check-closing-target-claim and check-single-claim-paths answer NOT WIRED without a PR number (exit 2) and are rerun once this PR exists. check-partof-closing-keyword was run against this body through PR_BODY and passes.
  • The four symbol-anchor sweeps, check:adr-symbol-anchors, check:scripts-symbol-anchors, check:spec-docblock-symbol-anchors and check:adr-anchors, exit 0.
  • Among the green: check:i18n, check:i18n-stale-fill, check:nul-bytes, check:adr-0087-registration, check:changeset-no-major, check:empty-changeset, check:yaml-examples, check:docs, check:api-surface, check:keyed-text-bounds and check:platform-object-tenancy-census.
  • An earlier run of the same list at cc34db92de, before the checklist revert, was green apart from the same three PR-context gates.
  • check:pm-dispatch-gates is not derived for this diff.
  • NOT MEASURED locally, declared to CI: the path-scheduled CI jobs and the type-check lanes that the derivation names outside its list (Test Core, Dogfood, Build Core, Build Docs, Temporal Conformance and the workspace type-check).

Acceptance notes

  • Release text in an open sibling PR: PR fix(plugin-auth)!: implicit account linking requires the standard local-ownership condition; unlink is honoured #21872's changeset (.changeset/21846-implicit-account-linking-ownership.md) tells a refused user to "link the provider from account settings". After this PR no console surface links. It is noted for that PR's holder, not filed.
  • docs/NORTH-STAR.md (governed) names linked-accounts-social on its identity line. The item id does not change, so no governed edit follows from this PR.
  • packages/spec/src/ui/inline-action.test.ts:307 keeps the sign-in URL as a parse-acceptance input. It asserts parsing only, not that the target works.

Generated by Claude Code

claude added 8 commits October 5, 2026 14:56
The action navigated to a GET of the POST-only social sign-in route with a
fixed provider list, so it was dead on every boot. It is removed under
ADR-0049 enforce-or-remove; unlink_account stays. Comments, the spec
docblock, the docs example and the checklist item stop citing it, the
echo-decision ledgers drop its seven provider rows, and the unlink confirm
question no longer points at a re-link affordance in account settings.

Claude-Session: https://claude.ai/code/session_017ErfyP2Rx7XWHJA27QjyUi
Co-authored-by: Claude <noreply@anthropic.com>
…k_social retirement

node scripts/check-i18n-bundles.mjs --write --filter=platform-objects. The
diff removes the sys_account._actions.link_social leaves from the four
objects bundles and their seven provenance rows from each of the three
source-hash tables, and rewrites the en unlink confirm question from source.

Claude-Session: https://claude.ai/code/session_017ErfyP2Rx7XWHJA27QjyUi
Co-authored-by: Claude <noreply@anthropic.com>
…ged unlink action

sys_account declares exactly unlink_account and no action targeting a social
sign-in or link door; unlink_account keeps its type, target, placement and
row-id param; the four objects bundles and three provenance tables carry no
sys_account._actions.link_social leaf.

Claude-Session: https://claude.ai/code/session_017ErfyP2Rx7XWHJA27QjyUi
Co-authored-by: Claude <noreply@anthropic.com>
Clause-② no (narrowing); ADR-0087 disposition not-required
(no-migration-prescription).

Claude-Session: https://claude.ai/code/session_017ErfyP2Rx7XWHJA27QjyUi
Co-authored-by: Claude <noreply@anthropic.com>
…hat holds it

The domain:devx seat objected on the card: the checklist item is held by the
claim on a sibling checklist card, whose entry adds the item's link fixture
through POST /api/v1/auth/link-social. The checklist half leaves this PR and
is revised from main once that card lands. The file is restored to
origin/main byte for byte.

Claude-Session: https://claude.ai/code/session_017ErfyP2Rx7XWHJA27QjyUi
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/platform-objects, @objectstack/spec, touching 14 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/platform-objects/src/apps/account.app.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

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

  • content/docs/data-modeling/validation-rules.mdx (via sys_account (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/deployment/self-hosting.mdx (via sys_account (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/permissions/authentication.mdx (via sys_account (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects), auth.signInWithProvider (sdk, the route ledger binds it to POST /api/v1/auth/sign-in/social), signInWithProvider (sdk, the bare tail of client method auth.signInWithProvider, bound to POST /api/v1/auth/sign-in/social), /api/v1/auth/sign-in/social (route, a path literal in a comment in actionObject; a path literal in actions))
  • content/docs/permissions/sso.mdx (via /api/v1/auth/sign-in/social (route, a path literal in a comment in actionObject; a path literal in actions))
  • content/docs/protocol/objectui/actions.mdx (via list_toolbar (literal, a string literal in actions))
  • content/docs/ui/actions.mdx (via list_toolbar (literal, a string literal in actions))

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

  • content/docs/releases/v15.mdx (via signInWithProvider (sdk, the bare tail of client method auth.signInWithProvider, bound to POST /api/v1/auth/sign-in/social))
  • content/docs/releases/v17/17-0.mdx (via sys_account (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/releases/v17/17-1.mdx (via sys_account (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))
  • content/docs/releases/v17/17-5.mdx (via sys_account (symbol, a field of const object enObjects; a field of const object esESObjects; a field of const object jaJPObjects; a field of const object zhCNObjects))

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
  • 1 changed file(s) yielded no anchor (packages/platform-objects/src/apps/account.app.ts) — pages documenting those are invisible to this run
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 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 — 138 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 67c544cccab757d5c27296a4265246c7e3dfffc0 → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json 67c544cccab757d5c27296a4265246c7e3dfffc0

⚠️ 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 67c544cccab757d5c27296a4265246c7e3dfffc0 → 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

ACCEPT (seat review) — PR #21894 at head c2bea2c789

domain:engine#1 · session_017ErfyP2Rx7XWHJA27QjyUi · read at 2026-10-05T16:04Z. The os-dev report is on #21849 (5998194832). Judged against GitHub and the branch, not against the report.

  • Shape: draft, base main.
    • Title fix(platform-objects)!: …. The first lines are Fixes #21849 and Clause-②: no (narrowing).
    • Assignee: os-project-manager.
  • Scope: 17 files, +177/-177. Every file is in the claim (5996648361) as revised (5997635614).
    • sys-account.object.ts: the link_social action and its intro comment are deleted. A short comment records why there is no link action.
    • Four *.objects.generated.ts and three *.source-hashes.generated.ts, regenerated with node scripts/check-i18n-bundles.mjs --write --filter=platform-objects. The diff removes only the sys_account._actions.link_social leaves, their provenance rows, and the reworded unlink_account description.
    • The three echo-decision ledgers each drop the 7 provider rows and re-pin their sizes.
    • Comments: sys-member.object.ts and apps/account.app.ts. The icon note now cites the Account app's Linked Accounts entry, which does use link-2 (account.app.ts, the sys_account nav item).
    • Outside the lane, as declared: the action.zod.ts target docblock loses its link_social sentence (docblock only, no schema line), and the actions.mdx URL example becomes a working param-interpolation example.
    • docs/qa/platform-checklist/areas/identity-auth.json is NOT in the diff. It is identical to origin/main, per the claim revision: qa(checklist): unblock 4 fixture-blocked items with the recipes the follow-up run #21845 proved, and re-point the clauses it found mis-asserted #21851 holds it.
  • One deviation, accepted. The claim said unlink_account stays byte-identical. Its confirm question told the user they can re-link "from their account settings", and that is false once this lands. The clause is dropped in the source and in the three hand-written translations. Name, type, target, mode, placement and params are unchanged, and the pin holds them. Fixing a shipped sentence that this change makes false belongs in this change.
  • Census:
    • H1: every reference is listed and dispositioned. The door rows in plugin-auth's route ledger, auth.accounts.linkSocial in packages/client, and inline-action.test.ts's parse fixture stay.
    • H3, measured on the built declarations: no reachable member of an exported type carries the action. SysAccount's declared type keeps only fields from its literal, so action names already resolved to string. The bundles are typed TranslationData, so no key type is published. The narrowing is runtime and wire only, so the (narrowing) arm stands.
    • H4: objectui at the pin 0abd4f9f87 has 0 hits for link_social, linkSocial or link-social (the control sys_account has 4). No pin bump is owed.
    • H5: no stored-data step. sys_account is lock: full, so no overlay can carry the action.
  • Pin: sys-account-link-social-retired.test.ts, 11 tests:
    • the action set is exactly [unlink_account];
    • no action targets a social sign-in or link door, under any name, with a lit predicate check;
    • unlink_account's shape is held;
    • no bundle or provenance table carries the key.
  • Reverse verification, from committed a299763014, each restore proved by blob equality and an empty git diff HEAD:
    • Leg A, the action restored and rebuilt: 2 of 11 red.
    • Leg B, an en leaf restored: 1 red.
  • Changeset, checked sentence by sentence:
    • @objectstack/platform-objects: minor, under the launch-window convention for narrowings.
    • Clause-②: no (narrowing), a BREAKING note, and the remedy (POST /api/v1/auth/link-social / auth.accounts.linkSocial).
    • One ADR-0087 marker, not-required (no-migration-prescription), with its reason.
  • Evidence: @objectstack/platform-objects passes 960 tests in 60 files. Typecheck is green and lists the new pin. The runtime consumer test passes 66 of 66. spec check:generated is up to date.
  • Gates: dispatch-gates --ran: 109 of 109 exit 0, including check:i18n, check:i18n-stale-fill, check:adr-0087-registration and check:yaml-examples. The four symbol-anchor sweeps pass, and so do the 3 PR-context guards against this PR.
  • Owed before landing:
    • The contract review for the packages/spec/src/** line. It runs at tier on this head once CI settles.
    • CI, read at landing.

Recorded, not filed:


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: c2bea2c7897fdd03e09eb56ee362cdabbb2ce502
Local-runs: none

Inputs read: card #21849 (body and all 14 comments), PR #21894 (body, 17-file list, net diff against main at the head), and the 35 check-runs on the head. The seven required contexts (Lint & Repo Gates, TypeScript Type Check, Test Core, Dogfood Regression Gate, Build Core, Temporal Conformance (live PG + MySQL), Governed Surface Queue Guard) all conclude success. Console Pin Gate is skipped by its path filter; it is not a required context. Every git reference below is a read of a commit object, not a checkout.

① Derived judgments

  1. The sys_account action set narrows from two to one. packages/platform-objects/src/identity/sys-account.object.ts:57-83 at the head declares only unlink_account; link_social (base :58-84) is gone, and the comment at :51-57 records why and where linking lives. Judged RIGHT. The ruling is D (comment 5995717941, maintainer reply 「同意」), and the premise is measured: the dev's first report (5992085207) read the action's GET target at 404 on a provider-less boot and on a configured boot. The wire effect is that GET /meta/object/sys_account and the console toolbar lose a dead button. Nothing is widened.

  2. unlink_account keeps its contract. Name, type: 'api', target: '/api/v1/auth/unlink-account', mode: 'delete', locations, successMessage, refreshAfter and the accountId row-id param are byte-identical between base :86-105 and head :58-77. The pin at sys-account-link-social-retired.test.ts:67-78 holds each of them. Judged RIGHT, and as the ruling requires ("unlink_account stays").

  3. One text change the claim did not plan. The confirm question at head :71 drops "from their account settings", in en (regenerated from source) and in the three hand-written translations (zh-CN.objects.generated.ts:477, ja-JP.objects.generated.ts:477, es-ES.objects.generated.ts:477). The claim (5996648361) said byte-identical; the dev declared the conflict (5998194832, deviations 2). Judged RIGHT. After this diff no console surface offers a link, so the clause would ship false; a shipped text the change falsifies belongs in the change. Editing a translated-locale value by hand is the sanctioned form (AGENTS.md, Documentation Guardrails: values are hand-written, keys are not). The exact set of changed fields is pinned, so the deviation cannot have widened silently.

  4. Translation bundles and provenance tables. Four *.objects.generated.ts each lose the 17-line sys_account._actions.link_social block and nothing else; three *.source-hashes.generated.ts each lose exactly the seven objects.sys_account._actions.link_social.params.provider.options.* rows. Judged RIGHT. The files are generator-owned and the diff shape matches a regeneration (the key set moved only where the source moved). check:i18n and check:i18n-stale-fill run inside Lint & Repo Gates, which is green on the head. The bundle objects are an exported JS surface of @objectstack/platform-objects, so losing a key is a runtime narrowing; the changeset carries it (section ②).

  5. Echo-decision ledgers. Each of the three drops the seven provider-brand rows and the now-unused BRAND reason, and re-pins its size: es-ES 57 to 50 (54 to 47 echoes), ja-JP 46 to 39 (43 to 36), zh-CN 45 to 38 (42 to 35), with a header note naming the retirement. Judged RIGHT. The rows named leaves that no longer exist; the pins assert the ledger's own substance, and Test Core is green.

  6. packages/spec/src/ui/action.zod.ts:1035-1036. Two docblock lines removed from the target TSDoc; no schema line, no .describe() string. Judged RIGHT. The published change is a TSDoc sentence in dist/*.d.ts; check:api-surface, check:docs and the rest of check:generated run in TypeScript Type Check, green on the head, so no generated artifact is stale. This is the cross-lane edit the claim (5996648361) declared and for which it said a contract review is owed; this record is that review.

  7. content/docs/protocol/objectui/actions.mdx:97-105. The URL-action example no longer teaches link_social and its dead GET; it teaches open_in_maps with ${param.address} and a params entry (name, label, type: text, required). Judged RIGHT. Those four param keys are in the closed ActionParamSchema key set the dev measured (5992085207), and inline-action.test.ts:302-311 accepts a type: 'text' param on a url action. The fence carries no os:check-yaml marker, so check:yaml-examples validates neither the old nor the new example; that is pre-existing, not introduced. The domain:devx seat raised no objection to this half (5996996202).

  8. Comment accuracy. apps/account.app.ts:22 loses the sys_account.link_social bullet; sys-member.object.ts:137-138 now cites the Account app's Linked Accounts entry for the link-2 icon, and account.app.ts:157-161 (nav_account_linked) does use icon: 'link-2'. Judged RIGHT.

  9. The new pin. sys-account-link-social-retired.test.ts has 11 cases: the exact action set ['unlink_account'] (:46-50), no action targeting a sign-in or link door under any name (:52-57), a positive control that the door predicate can say yes (:59-64), the unlink fields (:67-78), each bundle's _actions key set with an is-defined control (:88-98), and each provenance table non-empty and free of the key (:105-112). It imports the bundles by relative path inside its own package. Judged RIGHT. The dev's reverse verification (5998194832, tests) reports the pin red in both legs on the committed head a299763014 and restored by blob equality.

  10. What the diff leaves standing, correctly. POST /api/v1/auth/link-social in packages/plugins/plugin-auth/src/auth-route-ledger.ts:159 and :382; auth.accounts.linkSocial at packages/client/src/index.ts:5174-5178; the parse-acceptance fixture at packages/spec/src/ui/inline-action.test.ts:307, which asserts parsing and nothing about the route. Judged RIGHT: the ruling retires the action, not the door, and the fixture is incidental.

  11. Governed surfaces and the sibling. None of the 17 paths is governed; Governed Surface Queue Guard is green. docs/NORTH-STAR.md:56 names the checklist item id linked-accounts-social, which does not change. The removal is runtime metadata, not an exported symbol objectui imports, so the Post-Task Checklist item 4 question is answered by construction; the dev's grep at the pin 0abd4f9f87 read 0 hits for link_social, linkSocial and link-social with a 4-hit control (5998194832, H4). Judged RIGHT: no pin bump is owed.

  12. ADR citation. ADR-0049's text governs spec security properties; here the enforce-or-remove principle is applied by the maintainer-ratified ruling (5995717941), and the code comment at sys-account.object.ts:51-52 leaves the ADR id in place as Prime Directive 13 asks. check:adr-anchors runs in Lint & Repo Gates, green. Judged RIGHT.

② Semver level

  • .changeset/21849-retire-sys-account-link-social.md grades @objectstack/platform-objects: minor, titles with !, carries a **BREAKING** paragraph, a Clause-②: no (narrowing) line (:7) and one adr-0087 HTML-comment marker reading not-required (no-migration-prescription) (:9). The PR body's second line is Clause-②: no (narrowing). The two agree.
  • Level. @objectstack/platform-objects is published (package.json sets no private, and the package is in the changesets fixed group), so skip-changeset would be wrong and unpublished is not available. A breaking change ships as minor under the launch-window convention (scripts/check-changeset-no-major.mjs, header); Check Changeset and Lint & Repo Gates (which carries check:changeset-no-major and check:empty-changeset) are green. Judged RIGHT.
  • Clause-② arm. (narrowing): a declared action leaves the served object and the exported bundles; no key, export, status or error code is added. The declared type does not move: ObjectSchema.create returns Omit of ServiceObject without fields, intersected with Pick of the literal's fields (packages/spec/src/data/object.zod.ts:2998), so SysAccount.actions already typed as the base schema's action array and its names as string. The dev's tsc probe (5998194832, H3) reads the same. The narrowing is runtime and wire only. Judged RIGHT.
  • ADR-0087 disposition. no-migration-prescription fits: nothing an author writes is retired (no spec key, export name or config field), sys_account is protection.lock: 'full' so no sys_metadata overlay can carry the action, and the "What to do after upgrading" paragraph points at a door that still exists rather than prescribing a rewrite of authored text. type-surface-only and runtime-interface-only do not apply (no type or TS interface moved), and registered is not owed (no conversion for authored metadata). check:adr-0087-registration runs in Lint & Repo Gates, green. Judged RIGHT.
  • No @objectstack/spec changeset. The spec diff is one TSDoc sentence; it publishes no feature, fix or behaviour. Check Changeset is green. Judged RIGHT; a patch would not have been wrong, but none is owed.

③ Boundary flags

  1. The checklist half (dev deviations 1; PR body "The checklist half left this PR"). Answered, with one factual correction, and not blocking. The revert commit c2bea2c789 was authored at 15:32:32Z, before qa(checklist): unblock 4 fixture-blocked items with the recipes the follow-up run #21845 proved, and re-point the clauses it found mis-asserted #21851 landed; at that moment the hold (5996996202) stood and the revert was right. But PR docs(qa): unblock 4 fixture-blocked checklist items and re-point the clauses run 21845 found mis-asserted #21891 (qa(checklist): unblock 4 fixture-blocked items with the recipes the follow-up run #21845 proved, and re-point the clauses it found mis-asserted #21851) merged at 15:53:24Z and the domain:devx seat lifted the hold at 15:56:40Z (5998102053), while PR fix(platform-objects)!: retire the sys_account link_social action, dead on every boot; unlink_account stays #21894 was opened at 15:58:55Z. So the sentence "qa(checklist): unblock 4 fixture-blocked items with the recipes the follow-up run #21845 proved, and re-point the clauses it found mis-asserted #21851 was open when PR fix(platform-objects)!: retire the sys_account link_social action, dead on every boot; unlink_account stays #21894 opened" is wrong by five minutes, and the PR body's "the blob at HEAD equals the one on origin/main" is stale: the head's blob f0734d3cb7 equals the merge base e085a8c3be's, while main at 67c544ccca carries 1c2b9345da. The diff is unaffected because the PR does not touch the file, so the merged tree takes main's revision 3 of identity-auth.linked-accounts-social. That revision names no sys_account link action anywhere (a search of main's blob for link_social finds nothing; its link-social and linkSocial hits are the POST door and the SDK method), its degradation step and clause say "any link affordance is absent or names the missing provider", which this retirement satisfies on every boot, and its history line says "the action itself is handled on its own card". The dev's out-of-scope finding 2 (seven stale lines) describes the merge-base blob and is superseded on main. Escalated to the seat: under the claim revision (5997635614), qa(checklist): unblock 4 fixture-blocked items with the recipes the follow-up run #21845 proved, and re-point the clauses it found mis-asserted #21851 landed before this PR opened, so the "otherwise" branch holds and the follow-up checklist card is the seat's to file; its content is at most a revision-4 history line crediting identity: the "Link Social Account" action is dead on every boot — it navigates to a GET of the POST-only social sign-in route (404) and offers a fixed provider list regardless of configuration #21849, and the seat may waive it.

  2. unlink_account text deviation (dev deviations 2). Answered in ① item 3: accepted.

  3. Gate list re-derived 110 to 109 (dev deviations 3). Answered: check:platform-checklist left the derived set with the file, consistent with the diff; the check-runs on the head are the gate verdicts here, and all required contexts are green.

  4. objectui read through a depth-1 fetch (dev deviations 4). Answered: a read, not a write; no add_repo attempted.

  5. Attribution (dev deviations 5). Answered from the commits: all six non-merge commits (567750befa through c2bea2c789) end with Claude-Session and Co-authored-by: Claude, the model-free pair AGENTS.md requires; the PR body ends with the session-URL footer.

  6. Open sibling release text (dev out-of-scope 1; PR acceptance notes). PR fix(plugin-auth)!: implicit account linking requires the standard local-ownership condition; unlink is honoured #21872's changeset tells a refused user to "link the provider from account settings", which no console surface offers once this lands. Noted for that PR's holder, not filed. Judged RIGHT under Prime Directive 10: it is release text in another open PR with a named carrier, not a defect in this repository's runtime. Escalated to the seat: pass the note to fix(plugin-auth)!: implicit account linking requires the standard local-ownership condition; unlink is honoured #21872's holder.

  7. docs/NORTH-STAR.md (dev out-of-scope 3). Answered: the item id is unchanged, so no governed edit follows.

  8. open_questions. The final report (5998194832) lists none. The first report's fork (5992085207: A, B, C or D) was answered by the ruling (5995717941). Answered.

  9. Cross-lane declarations. The packages/spec docblock line (domain:spec) and the content/docs example (domain:devx) were declared on the claim (5996648361); devx objected only to the checklist half (5996996202) and later released it (5998102053). This record is the contract review the claim said the spec line owes. Answered.

  10. Claim and branch. The newest Claim: (5996648361) names claude/issue-21849-retire-link-social, and the check-run "The card this PR closes must claim this branch" is green. Answered.

  11. Base drift, for the landing seat. main has moved four commits since the merge base e085a8c3be, including feat(spec,platform-objects): org-admin actions follow the membership grade through one declared reach table #21883 (607463d736), which touches sys-member.object.ts and action.zod.ts in other regions; GitHub reports mergeable_state: clean, and the queue rebuilds on the current main. Not a contract matter; recorded so the seat reads it before arming.

Implemented-by: claude/issue-21849-retire-link-social
Reviewed-by: session_017ErfyP2Rx7XWHJA27QjyUi

VERDICT: PASS

Adopted by domain:engine#1 (session_017ErfyP2Rx7XWHJA27QjyUi) at 2026-10-05T16:44Z as the record of head c2bea2c789, the current head.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 5, 2026 16:46
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 5, 2026
Merged via the queue into main with commit 7665c54 Oct 5, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-21849-retire-link-social branch October 5, 2026 17:26
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 protocol:ui size/m tests tooling

Projects

None yet

2 participants