Repository navigation
fix(console,auth): the login and register pages offer sign-up only where the audience posture admits it (objectui#11691) - #11703
Conversation
…ere the audience posture admits it (objectui#11691) The console read only `emailPassword.disableSignUp`, which the server deliberately does not force from the audience posture. Under the default `invite_only` posture the login page offered "Sign up" and the register page refused the finished form with 403 SELF_REGISTRATION_CLOSED. Both pages now read `features.audiencePosture` beside `disableSignUp` through one decision (`pages/auth/signUpOffer.ts`): the generic sign-up is offered when the posture admits strangers (`open`, `email_domain`), when the visitor came from an invitation redirect, or when the deployment has no owner yet; otherwise `/register` explains that registration is by invitation before the form. A server that sends no posture is answered as before. `AuthPublicConfig.features` declares `audiencePosture` with the spec's `AudiencePosture`, and `@object-ui/auth` raises its `@objectstack/spec` floor to the first release that carries that type. Claude-Session: https://claude.ai/code/session_01FngvPpdrnhHMdHHq6vwwju Co-authored-by: Claude <noreply@anthropic.com>
…jectui#11691) A real AuthProvider over a real auth client against a stub server: under invite_only /login offers no generic "Sign up" and /register explains before the form; an invitation redirect still reaches a submitted registration; a fresh deployment keeps sign-up for its first owner; open, email_domain and a server without the posture key are unchanged; and disableSignUp: true still hides everything. The restated posture predicate is checked against the spec's own for every declared posture. Claude-Session: https://claude.ai/code/session_01FngvPpdrnhHMdHHq6vwwju Co-authored-by: Claude <noreply@anthropic.com>
…ctui#11691) The console type-check covers test files: the posture fixture is typed as the wire config (one cast, for the off-vocabulary values a newer server could send), and the last-route read avoids `Array.prototype.at`, which the console's ES2020 lib does not declare. Claude-Session: https://claude.ai/code/session_01FngvPpdrnhHMdHHq6vwwju Co-authored-by: Claude <noreply@anthropic.com>
…1691) Claude-Session: https://claude.ai/code/session_01FngvPpdrnhHMdHHq6vwwju Co-authored-by: Claude <noreply@anthropic.com>
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: Inputs read: card #11691 (body and all 8 comments, the two triage amendments 6010280240 and 6011941985 and the seat's ACCEPT 6012337738 included); PR #11703's body, its 9-file list and the net diff against ① Derived judgmentsEvery accept-set and public-surface change the diff implies, named and judged:
② Semver level
Clause-②: yes ③ Boundary flagsFrom the dev report 6011484768 on the card:
Check-runs on the head: 45, of which 42 Implemented-by: VERDICT: PASS Generated by Claude Code |
… bare apiMethods-card number (objectui#10803, batch 7) (objectstack-ai#10962) Part of objectstack-ai#10803 Clause-②: no Dispatched implementation of the `domain:ui` seat objectstack-ai#1 claim (comment `5867587761`) on objectui#10803, batch 7, session `https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk`. Citations only: no sentence's claim moves, and every edited pending changeset's frontmatter is byte-identical. The only runtime text that moves is two console warnings, one in `@object-ui/app-shell` and one in `@object-ui/plugin-detail`, which lose their dead pointer and nothing else (amendment `5860244997`, Q1 = A; `patch` changeset). No test pins any changed text: the literal-anchor sweep below finds no specific anchor, so no test file is edited. This batch carries release `5866922219`'s two lists: - the **30 `objectstack#N` citations that answer 404**, in the card's two classes (pending changeset prose and non-test `packages/*/src`); - the **28 bare `objectstack-ai#3391` lines in 10 files** that mean objectstack's apiMethods whitelist card, which batch 6 fixed at 2 other sites. ## Why `Part of`, not a closing line Both lists read **0** after this batch (**Census**). The brief's rule was a closing line if the card's lists all read 0. They do, but reading the sentences found **10 more lines in the same two classes that cite a dead objectstack number written bare**, three numbers in all (**Acceptance notes** 1). Triage item 3 puts a dead number found later in these classes on this card, so the card is not finished. Whether it carries them as a batch 8 or closes is the seat's call. ## Premise, re-measured on `origin/main` `3b469c8ea` (the branch point) - **Every distinct `objectstack#N` in the two in-scope classes.** 372 numbers (the one objectstack issue URL in these classes names 6227, which is among them). Each was read once with REST `GET /repos/objectstack-ai/objectstack/issues/N`: - 341 answer 200; - 1 answers 301: objectstack#14026, transferred to objectui#10102, which batch 6 re-qualified; - **30 answer 404**, exactly the 30 batch 6 listed. - **The 30, read again.** A second `issues/N` read of each answers 404 (30 of 30), and `pulls/N` answers 404 for all 30. Lit controls in the same run: objectstack#3391, objectstack-ai#3720 and objectstack-ai#3546 answer 200 as issues, and `pulls/13267` answers 200. - **objectstack-ai#3391 and objectstack-ai#3546, both repositories.** - objectui#3391 is the record-header api-action placeholder card, unrelated. - objectstack#3391 is the apiMethods whitelist contract card: "UI 操作按钮与 apiMethods 白名单一致性契约落地". Its body names the effective operation set, `/me/permissions`, the 405 import refusal and export derived from list, which is what each of the 28 sentences says. - objectui#3546 is the missing-i18n-keys card, unrelated. - objectstack#3546 is "detail/form 面的 edit/delete 按钮接入服务端 effective 操作集", the inline-edit gate the two paired lines describe. - **objectstack history.** Read from a full, not shallow, treeless clone of objectstack `main` (`git rev-parse --is-shallow-repository`: false). - Every objectstack sha this PR cites is an ancestor of objectstack `main` (`git merge-base --is-ancestor`, exit 0): the 24 this PR adds to the tree, the 3 its edited sentences already cited (`c459da6bc`, `89448a52b`, `9bd4344e4`), and the 7 this body names besides. - `git rev-parse --short=9` returns the same 9 characters for each. - Control legs in the same clone: the head of the open PR objectstack#20421 (`a22b90fc0`) answers exit 1; the known ancestor `51789064` answers exit 0. - **The one objectui sha.** `7a197e7c5` is an ancestor of the branch point, exit 0. Control legs: the head of PR objectui#10945 answers exit 1, and `5f789538d` answers exit 0. This checkout is not shallow. - **A cross-check, not the method.** objectstack's own sweep of dead tracker citations in its tree (objectstack#19123's landing `66e266c93`, its stages `21ab41041`, `5cf58eb16` and `0d7ed5a37`, and `f415bcf18`) anchored eight of these numbers in its own files. For each of the eight (objectstack-ai#5970, objectstack-ai#6483, objectstack-ai#9934, objectstack-ai#10485, objectstack-ai#11330, #11846, #12868 and #17147) it chose the same commit this PR cites. - Every edited changeset is pending: it is present in `.changeset/` on `main`. ## Census (the enumeration pin for this batch) The 30 numbers (REF = a commit or tree): ``` git grep -nE 'objectstack#(5970|5976|6038|6124|6281|6331|6450|6483|6515|9933|9934|10354|10485|10695|11330|11507|11513|11658|11703|11753|11846|12009|12868|13117|13670|16126|17147|17762|17987|18012)([^0-9]|$)' REF -- '.changeset/*.md' 'packages/*/src/**' ':!**/__tests__/**' ':!**/*.test.*' ':!**/*.spec.*' ':!**/test/**' ':!**/tests/**' | wc -l ``` The bare `objectstack-ai#3391`, with the same pathspec: ``` git grep -nE '(^|[^0-9A-Za-z_#/])objectstack-ai#3391([^0-9]|$)' REF -- '.changeset/*.md' 'packages/*/src/**' ':!**/__tests__/**' ':!**/*.test.*' ':!**/*.spec.*' ':!**/test/**' ':!**/tests/**' | wc -l ``` | REF | the 30 | bare `objectstack-ai#3391` | |:--|:--|:--| | `3b469c8ea` (branch point) | **82**: 26 changeset lines in 24 files, 56 src lines in 39 files | **28** in 10 files | | `c292a6400` (this head) | **0** | **0** | | this head merged with `main` `5c94589f0` (`git merge-tree --write-tree`, clean, tree `bba27eece`) | **0** | **0** | | `5c94589f0` (`main` alone) | 82 | 28 | - Lit controls on the same instruments at this head: live objectstack#10856 reads 4 lines (4 at the branch point); `objectstack#3391` reads 32 lines, against 3 at the branch point (28 re-qualified here, and this PR's sweep changeset names it once). - **Out of scope, as it stands** (whole tree at this head, unfiltered): - the 30 numbers: 84 test lines in 47 files, 1 scripts line, 2 lines in 2 `apps/console` files, 2 package READMEs (`auth`, `react`), 11 lines of published `CHANGELOG.md` history in 8 files; 0 `.github`, 0 governed, 0 `content/docs`; - bare `objectstack-ai#3391`: 12 test lines in 9 files and 23 `CHANGELOG.md` lines in 8 files. ## Citation form - **An objectstack commit** is written the way batch 6 wrote its stand-ins: objectstack and the 9-character backticked sha of the commit on objectstack `main` that landed the change the sentence rests on. - **A dead card beside its own dead pull request** collapses to that pull request's squash commit (objectstack-ai#5970 with PR objectstack-ai#6450, objectstack-ai#10485 with PR objectstack-ai#10695). - **A dead number beside the live landing it already names** is dropped: #11846 beside PR objectstack#12718, #16126 beside PR objectstack#16920, #12868 beside objectstack `c459da6bc`, and #12009 beside objectstack `89448a52b` (**Special cases** 3). - **A ruling the dead card carried** is cited by its date, with the commit that executed or recorded it, as batches 3 to 6 did for objectui rulings. - **One sentence cites this repository's commit**, `7a197e7c5`, because the change it names landed here (objectstack-ai#6331). - **Runtime text carries no sha.** In the two console warnings the dead pointer is dropped (**Special cases** 8). - **The bare `objectstack-ai#3391`** becomes `objectstack#3391`, and on the two lines that write `objectstack-ai#3391/objectstack-ai#3546`, `objectstack#3391/objectstack#3546`. ## Mapping, the 30 numbers Lines / files are the branch-point census for that number (a line naming two of them counts under both). | dead number | resolution | what that commit carries | lines / files | |:--|:--|:--|:--| | objectstack-ai#5970 | objectstack `97e7e3caa` | "unify ActionSchema.visible/disabled on one condition shape (objectstack-ai#6450)", body "(objectstack-ai#5970)": `visible` gains the boolean arm | 2 / 1 | | objectstack-ai#6450 (PR) | the same `97e7e3caa`, its squash | as above; the card / PR pair collapses | 2 / 1 | | objectstack-ai#5976 (PR) | objectstack `795b6e1aa`, its squash | "5 值子集改名 `HttpMethodSubset`" | 1 / 1 | | objectstack-ai#6038 | objectstack `7618ee814` | "key a container's default `list` `_views` name by the runtime identity": leg 2 of 3 of the objectstack#5164 ruling, the `packages/lint` half | 1 / 1 | | objectstack-ai#6124 (PR) | objectstack `b3c1f3cd5`, its squash | "key `_views` translations by the runtime view identity"; "The extractor now ASKS the composer for the key" | 1 / 1 | | objectstack-ai#6281 (PR) | objectstack `85ec26d28`, its squash, 2026-08-07 | "SDUI props — enforce or remove (objectstack-ai#5775) (objectstack-ai#6281)": the shared `PageContainerProps`, whose single key is `children`, for `page:section` / `page:footer` / `page:sidebar`, which were `EmptyProps` | 1 / 1 | | objectstack-ai#6331 | objectui `7a197e7c5` | this repository's "SchemaForm reads the canonical `visibleWhen`, reviving every metadata-form predicate (objectstack#6331)" | 1 / 1 | | objectstack-ai#6483 | objectstack `ee58392e1` | "ADR-0005 白名单强制 … (objectstack-ai#6483)"; its diff carries the sentence the comment quotes, "Runtime-created sets … ride `allowRuntimeCreate` (still `true`) and keep working" | 1 / 1 | | objectstack-ai#6515 (PR) | objectstack `2fdb36eb9`, its squash | "SpecifierSchema gains a closed `valueDomain` enum": "`bcp47_locale` is deliberately not in the vocabulary", because `localization.locale`'s options ARE the shipped catalogs | 1 / 1 | | objectstack-ai#9933 | objectstack `d5552ca13` | "admit columnState as an explicitly runtime-only view-overlay key" (subject ending "(objectstack-ai#9996)"; "(objectstack-ai#9933)" is on the message's first body line), on the overlay faces including `viewItemWireFields` | 3 / 3 | | objectstack-ai#9934 | objectstack `79c46da90` | "producer-side user-facing marking for hook refusal messages — userMessage channel (objectstack-ai#9934)": `ApiErrorSchema.userMessage`, the contract half of the objectui#5210 split | 10 / 9 | | objectstack-ai#10354 (PR) | objectstack `9e04c3e35`, its squash | "let the publish door state the package it is promoting"; its changeset and code comment carry the key-presence / `no_draft` warning `ResourceEditPage.tsx` points at | 4 / 3 | | objectstack-ai#10485 | objectstack `35ad101bc` | "retire the `themes` carrier key and ThemeSchema (objectstack-ai#10485, ADR-0049) (objectstack-ai#10695)": "Ruled B (退役授权面, 2026-08-21)", "delete ui/theme.zod.ts whole" | 15 / 13 | | objectstack-ai#10695 (PR) | the same `35ad101bc`, its squash | as above; the card / PR pair collapses | 6 / 6 | | objectstack-ai#11330 | objectstack `a9ee98992` | "manifest.runtime trust-tier text states publish-gate-only enforcement truthfully", the trust-tier half (**Special cases** 2) | 1 / 1 | | objectstack-ai#11507 | objectstack `88b9d749a` | "declare sys_activity.type as an open, author-extensible vocabulary": "Maintainer ruling 2026-08-24, direction 4" | 13 / 9 | | objectstack-ai#11513 | objectstack `e170b0ae5` | "lock package-declared permission sets at the save door; clone to customize", quoting the 2026-08-24 ruling 「同意 第一步(创业阶段,Salesforce 式)」 | 3 / 3 | | objectstack-ai#11658 | objectstack `1a6a19c31` | "open RecordActivityProps.types to author-contributed activity kinds"; its message names objectstack-ai#11658 as the card it settles, and it executes the 2026-08-24 ruling | 1 / 1 | | objectstack-ai#11703 | objectstack `5cb62d88b` | "make clone_permission_set carry all five copied facets"; its message names objectstack-ai#11703 as the card it settles: the silent-grant-loss shape | 1 / 1 | | objectstack-ai#11753 | "the 2026-08-25 ruling whose spec half is objectstack `0e4e51b0a`" | `ActionParamSchema.carryOver`, whose changeset reads "(objectstack-ai#11753 ruling, spec half; #11992)" and "The maintainer's 2026-08-25 ruling on objectstack-ai#11753" | 2 / 2 | | #11846 | objectstack `0c2334f6c`; dropped beside PR objectstack#12718 | "retire preview mode — the RuntimeMode 'preview' value and the whole PreviewModeConfig block (#12718)" | 3 / 3 | | #12009 | dropped beside objectstack `89448a52b` | the card of the `AUTH_SSO_PROVIDER_SCHEMA` removal, whose landing the line already cites (**Special cases** 3) | 1 / 1 | | #12868 | dropped beside objectstack `c459da6bc` | the line already cites the commit that executed the ruling; objectstack's own `f415bcf18` anchors #12868 to the same `c459da6bc` | 1 / 1 | | #13117 (PR) | objectstack `225e7690f`, its squash | "Readiness read for the Phase-2 members … global:search and global:notifications both have shipped platform data sources, so per the ruling both STAY declared" | 1 / 1 | | #13670 | "maintainer ruling 2026-08-31, option 2, recorded in objectstack `8c6a7fc0b`" | "The #13670 ruling settled the question: text's intended evaluation channel is `content` alone" | 1 / 1 | | #16126 | dropped beside PR objectstack#16920 | PR objectstack#16920 (200) names #16126 in its body as the card it settles; merged 2026-09-08 as `859ded3ec` | 2 / 2 | | #17147 | objectstack `aaacf1d5c` | "the install-time granted permission set is REGISTERED at load and refuses nothing — say so, and pin the measurement (#17147)", the measurement on `9bd4344e4` | 2 / 2 | | #17762 | objectstack `4342c9923` | "guard three data lookups against Object.prototype fall-through"; its message names #17762 as a card it settles, `classifyFilterToken` among the three lookups | 1 / 1 | | #17987 | objectstack `e233db9db` | "declare element-level `navigation` on object-kanban / object-calendar …"; its message names #17987 as the card it settles, and its Downstream note: objectui#8652 waits on it, unlock criterion a released, installable `@objectstack/spec` (**Special cases** 1) | 2 / 2 | | #18012 | objectstack `176b03582` | "`$between` requires two non-blank endpoints (#18012)": "Ruling executed: decision batch objectstack-ai#146 item 5, **letter A**" | 3 / 3 | The 28 `objectstack-ai#3391` lines, all now `objectstack#3391`: `ObjectDataPage.tsx` (3) and `ObjectView.tsx` (2) in app-shell; `managedBy.ts` (5); `MePermissionsProvider.tsx` (2), `PermissionContext.ts`, `PermissionProvider.tsx`; `fieldWriteGate.ts`; `ImportWizard.tsx` (6), `ObjectGrid.tsx` (4); `ListView.tsx` (3). Each was read: every one names the server's effective API operation set, `/me/permissions` `apiOperations`, or the 405 import refusal. ## Special cases (the judgement calls) 1. **#17987, two sentences.** - `ObjectTree.tsx`: "blocked on objectstack#17987, whose unlock criterion is a released `@objectstack/spec` carrying the declaration being installable here" becomes "blocked on objectstack `e233db9db`, whose unlock criterion …". That commit's Downstream note states the same criterion. - `ObjectCalendar.tsx`: "that card is `pm:blocked` on objectstack#17987" becomes "that card waits on objectstack `e233db9db`". The label word is not kept, because objectui#8652's label reads `pm:on-hold` today (measured); "waits on" is the phrase `e233db9db`'s own note uses for that card. 2. **objectstack-ai#11330.** "it is objectstack#11330's half of the same panel" becomes "it is the trust-tier half of the same panel, which objectstack `a9ee98992` settled separately". `aaacf1d5c`'s message calls objectstack-ai#11330 "the sibling half of this very sentence", ruled the same way on 2026-08-30, and `a9ee98992` (2026-08-30) is that half's landing. 3. **#12009 collapses into the sha beside it.** objectui#6910's body and ruling comment `5534414562` name "objectstack#12009 / PR #13413" together as the one `AUTH_SSO_PROVIDER_SCHEMA` precedent, a card and its pull request. Batch 6 replaced PR #13413 with its squash `89448a52b`, so the card goes the way of batch 3's objectstack-ai#5401 / objectstack-ai#5505 pair. 4. **objectstack-ai#11753, two sites.** The card carried the ruling, and `0e4e51b0a` is its spec half. Both sites keep "ruling" as the antecedent that `ActionParamDialog.tsx`'s next paragraph ("The ruling's point …") reads. 5. **objectstack-ai#10354 in `ResourceEditPage.tsx`.** "since objectstack#10354 `doPublish` states" gains a comma, "since objectstack `9e04c3e35`, `doPublish` states", so two adjacent code spans do not read as one. 6. **objectstack-ai#11507 in the 8137 changeset.** "objectstack#11658 executing the maintainer's 2026-08-24 ruling on objectstack#11507" becomes "objectstack `1a6a19c31` executing the maintainer's 2026-08-24 ruling": the executing commit is named, and the ruling is cited by its date. 7. **Line breaks moved** where the stand-in is longer or shorter: `ActionRunner.ts` (two sites), `ActionParamDialog.tsx`, `theme.ts`, `theme.zod.ts` (two sites), `index.zod.ts` and the metadata-admin `i18n.ts` comment, where "ruling on" became "ruling of 2026-08-24,". 8. **The runtime strings.** Only the listed text moves. | file | member | before | after | |:--|:--|:--|:--| | `app-shell/src/layout/activityItemType.ts` | the `console.warn` in `warnUnmappedActivityType` | "… `sys_activity.type` is author-extensible (objectstack#11507, ruled 2026-08-24) and is not validated on write …" | "… `sys_activity.type` is author-extensible (ruled 2026-08-24) and is not validated on write …" | | `plugin-detail/src/renderers/recordActivityFeed.ts` | the `warnOnce` message in `warnUnknownActivityType` | "… `sys_activity.type` is author-extensible (objectstack#11507, ruled 2026-08-24) and is not validated on write …" | "… `sys_activity.type` is author-extensible (ruled 2026-08-24) and is not validated on write …" | No test, doc or changeset quotes either message with the pointer: the census reads 0 in `.changeset/`, and the anchor sweep finds no test literal that drops. 9. **`objectstack-ai#3391/objectstack-ai#3546`.** On the two lines that pair them (`managedBy.ts`, `ObjectGrid.tsx`), both halves are qualified, as batch 6 qualified both halves of "#13337/#13086". The other bare `objectstack-ai#3546` lines are not in this batch's lists and are left (**Acceptance notes** 2). ## The literal-anchor sweep (both test-pin classes, ruling `5861900779`) - **Instrument.** Every string, template and regex literal in all 4073 tracked test and script files (106544 distinct literals), read with the TypeScript scanner. - **Candidate filter.** A literal is a candidate if it matches the diff's removed lines with two lines of context, raw or comment-flattened: 1983. - **Test.** Does its occurrence count DROP between `3b469c8ea` and `c292a6400` in any of the 74 changed files, raw or comment-flattened? 136 do. - **Every one is generic:** digits, punctuation, single words ("object", "blocked", "locked"), character classes, and two regexes that read no changed file: `/objectui#\d+|objectstack#\d+/` in `registry-inputs-spec-parity`, which asserts over its own ledger's reasons, and the older spelling of the three submitRedirect tests' ruling matcher, quoted in their own doc comments (the live `CITES_ITS_RULING` asserts over their own refusal text). None is a changed phrase, a dead number or a changed warning. ## Held **By the serial rule: nothing.** Open PRs were mapped at branch time (9 open) and again after the push, before this PR opened (11 open). The second mapping came after the push, not before it; the same three files were shared both times. Three open PRs share a file with this PR: - _Both PRs below have merged since this PR opened (objectui#10945 as `06a96e948`, objectui#10908 as `b45d463a9`). The trial merge with today's `main` is clean, and both censuses read 0 on it (contract review `5870922323`), so nothing is owed. The two rows are kept as the record at the time._ - **objectui#10945, `RecordDetailView.tsx`.** The blob at its merge-base equals the branch point's. Its hunks are the imports and one block far below; this PR's one changed line in that file is far from both. - **objectui#10908, `types/src/zod/index.zod.ts`.** Its one insertion is in the export list, far below this PR's two changed comment lines. - **objectui#10278, `plugin-grid/src/ObjectGrid.tsx`.** The file drifted between its merge-base and the branch point, so this PR's four changed lines were mapped onto its merge-base by a line alignment: the nearest of its hunks is more than 150 lines from any of them. Trial merges with this head (`git merge-tree --write-tree`): - clean for objectui#10952, objectstack-ai#10950, objectstack-ai#10949, objectstack-ai#10947, objectstack-ai#10945, objectstack-ai#10944, objectstack-ai#10930, objectstack-ai#10908 and objectstack-ai#10777; - objectui#10278 conflicts in `ObjectGrid.tsx`, `plugin-grid/README.md` and `content/docs/plugins/plugin-grid.mdx`, and conflicts in the same three files against `main` alone; - objectui#5400 (Version Packages) regenerates and is not a hold. `.changeset/9954-read-rate-banner.md` is held by this seat's objectui#10913 dispatch (PR objectui#10949) and is untouched here. It carries none of this batch's numbers. ## Changesets - `.changeset/10803-dead-citation-sweep-seventh-batch.md`, EMPTY frontmatter. It covers the comment-only edits in 17 released packages; no published behaviour changes through them. It points at the second file for the runtime text. - `.changeset/10803-seventh-batch-runtime-strings.md`, `'@object-ui/app-shell': patch` and `'@object-ui/plugin-detail': patch`: the two warnings lose their pointer. What renders, and when and how often each warning fires, are unchanged. ## Proof of prose-only (C4), against `3b469c8ea` - **Source.** Each of the 48 touched `.ts` / `.tsx` files was parsed at `3b469c8ea` and at this head with TypeScript 6.0.3's `createSourceFile`, and re-printed by `createPrinter({ removeComments: true })`. - 46 of 48 prints are identical. - `activityItemType.ts` and `recordActivityFeed.ts` are equal once the one listed substitution each (**Special cases** 8) is applied to the base print, each matched once. - 0 parse diagnostics. - Lit controls on the same instrument: editing a string literal moves the print; re-spacing a comment does not. - **Changesets.** The frontmatter block of every one of the 24 edited changesets is byte-identical at `3b469c8ea` and this head (24 of 24, md5). The overwrite gate below agrees. - **Scope of the diff:** 74 files, +157 / −115: 24 edited and 2 new changesets, and 48 non-test source files in 17 released packages. No test file. ## Gates, on this head `c292a6400` Each line is the gate's own verdict and exit code, captured by redirect-then-`$?`. - `node scripts/check-changeset-presence.mjs`, exit 0: "48 source file(s) of 17 released package(s) changed, and this change declares 2 changeset(s): .changeset/10803-dead-citation-sweep-seventh-batch.md, .changeset/10803-seventh-batch-runtime-strings.md." - `pnpm changeset:check`, exit 0: "All workspace packages are in the changeset fixed group." / "No changeset declares a `major` bump." - `node scripts/check-changeset-overwrite.mjs` (report-only), exit 0: "2 changeset(s) added, 24 modified, 0 deleted". `declared at base` equals `declares now` for each of the 24. - `pnpm check:changeset-claims` (report-only), exit 0: - "Every one of those 1 address(es) either names the tree it was read from, or points at a line this change does not move"; - "Every package declared across those 22 body(ies) is either not negated …"; - the standing notice "87 pending changeset(s) describe a file this change touches". Read against the diff: a pending changeset quoting a replaced pointer would itself carry a dead number and sit in the census, which reads 0. - `pnpm check:control-bytes`, exit 0: "check-control-bytes: OK (scanned 9229 tracked text file(s); skipped 85 binary)." A `grep -P` control-byte self-scan of the 74 files finds none. - `pnpm check:new-line-citations`, exit 0: "VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0". - `pnpm check:pending-changeset-literals`, exit 0: "No test source names a pending changeset." - Also run over the touched comments: - `pnpm check:spec-symbols`, exit 0: "spec member citations: 1421 sources + 184 documentation pages; nothing cites a key its spec symbol does not declare."; - `pnpm check:installed-pin-claims`, exit 0 ("OK"); - `pnpm check:comment-mask-corpus`, exit 0 (1 disagreeing file, within the ceiling objectui#7882 holds open); - `node scripts/check-hand-rolled-comment-mask.mjs`, exit 0 ("OK every carrier is a DEBT entry, and every DEBT entry still carries one."); - `pnpm check:handler-key-reads`, exit 0 ("every judged read is a declared member of it"). - The governed-surface predicate over the 74 paths, exit 0: "NOT GOVERNED — 74 path(s) checked against 5 governed surface(s); none matched." Lit control `AGENTS.md`: exit 3. **Tests and type-check**, through the shared verify lock, on `c292a6400`. Each is `VERDICT command-exit 0`. - `scripts/__tests__/`, the whole directory, whose whole-tree scanners read the touched files and changesets: `Test Files 177 passed | 2 skipped (179)`, `Tests 5332 passed | 2 skipped (5334)`. The two skipped files are the network-escape fixtures that run only as a child. - `packages/types/`, `core/`, `react/`, `i18n/`, `providers/`, `permissions/` and `data-objectstack/`, whole packages, in one run: `Test Files 704 passed (704)`, `Tests 13170 passed | 13 skipped (13183)`. - The eight touched plugin packages (calendar, designer, detail, form, grid, kanban, list, tree): `Test Files 787 passed | 1 skipped (788)`, `Tests 7521 passed | 27 skipped (7548)`. - `packages/components/`: `Test Files 324 passed | 1 skipped (325)`, `Tests 3148 passed | 24 skipped (3172)`. - `packages/app-shell/`: `Test Files 854 passed | 1 skipped (855)`, `Tests 8789 passed | 9 skipped (8798)`. - Type-check: `turbo run build` of the 28-package dependency closure (`Tasks: 28 successful, 28 total`), then `pnpm --workspace-concurrency=2` with the 17 package filters `run type-check`: 17 script echoes, 17 `Done`. A first attempt before the build exited 2 on an unbuilt dependency (`Cannot find module '@object-ui/types'`) and measured nothing. - No red leg: the sweep found no anchor to move, so there is no pin whose old copy should fail. CI on `c292a6400`: 43 check-runs, 40 success, 3 skipped, 0 failed; `Spec Main Shape Gate` success. ## Acceptance notes 1. **Dead objectstack numbers written bare: 10 more lines in the same two classes.** A bare number resolves to this repository, where each of these is a live, unrelated card, so no `objectstack#` census sees them. - **Measurement.** The bare-number instrument of PRs objectui#10875 / objectstack-ai#10892 / objectstack-ai#10914 reads 965 distinct numbers at this head. The 916 between 100 and 25000 were each read once as objectstack issues: 877 answer 200 and 39 answer 404. Reading the sentences of those 39, three mean an objectstack card or pull request (below); the other 36 cite objectui cards or objectui pull requests. - **objectstack-ai#9934**, 7 lines in 7 files: `.changeset/7980-agent-key-envelope-read.md`, and in app-shell `index.ts`, `apiErrorEnvelope.ts` (2), `PackageFormDialog.tsx`, `StudioDesignSurface.tsx` and `packages-io.ts`. All mean the `userMessage` channel, objectstack `79c46da90`. - **"PR objectstack-ai#6281"**, 2 lines in `containers.tsx`, beside objectstack#5775. The landing is objectstack `85ec26d28`. - **"objectstack PR objectstack-ai#8452"**, 1 line in `useRecordCrudVerdicts.ts`. The landing is objectstack `27358d517` ("add batch recordIds to security/explain (objectstack-ai#8326) (objectstack-ai#8452)"). - None of them sits in a sentence this PR edits, so they are outside this batch's lists and left. Carrier: this card, triage item 3. 2. **The rest of the bare `objectstack-ai#3546` population.** Five more comment lines write objectstack#3546 as a bare `objectstack-ai#3546` (`RecordDetailView.tsx` 2, `RelatedRecordActionsBridge.tsx`, `record-details.tsx`, `fieldWriteGate.ts`), and one writes it as `objectui#3546` (`plugin-detail`'s `index.tsx`). Each names the server's effective API operation set on a detail or form surface. It is live-but-wrong, not a 404, like the `objectstack-ai#3391` class this batch closed. Carrier: none. 3. **A stale label in a comment.** The `ObjectCalendar.tsx` sentence said objectui#8652 is `pm:blocked`; that card reads `pm:on-hold` today. **Special cases** 1 says how the repaired sentence avoids the label. 4. **Filenames are not citations.** `.changeset/17147-plugin-disclosure-not-enforced.md` carries one of the 30 numbers in its name; it stays, as in PRs objectui#10707, objectstack-ai#10797, objectstack-ai#10854, objectstack-ai#10869, objectstack-ai#10875, objectstack-ai#10892 and objectstack-ai#10914. --- _Generated by [Claude Code](https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk)_ _Tests block completed by the `domain:ui` seat objectstack-ai#1 from the dev report `5870507620` (the suites that finished after this PR opened), and three figures corrected after contract review `5870922323` (the objectstack-ai#9933 and objectstack-ai#6515 quotes, and the Held rows); no code claim moved._ Co-authored-by: Claude <noreply@anthropic.com>
Fixes #11691
Clause-②: yes
What this changes
/api/v1/auth/configstates the sign-up rule as two keys:emailPassword.disableSignUp(the hard off switch) andfeatures.audiencePosture(who may self-register). The server deliberately does not force the first from the second: underinvite_onlyits sign-up route still admits a pending invitee. The console read only the first, so under the defaultinvite_onlyposture/loginoffered "Sign up" and/registerrefused the finished form with403 SELF_REGISTRATION_CLOSED.Both console pages now decide through one shared function,
decideSignUpOfferin the newapps/console/src/pages/auth/signUpOffer.ts:/login/registerdisableSignUp: true/loginas before, invitation redirects includedopenoremail_domaininvite_only, reached from an invitation (?redirect=/accept-invitation/ID)invite_only, deployment with no owner yet (bootstrap-statusanswershasOwner: false)invite_only, otherwiseaudiencePosturekey (older server)disableSignUpalone decides, as before@object-ui/auth:AuthPublicConfig.featuresdeclaresaudiencePosture?: AudiencePosture(the type from@objectstack/spec/system), besidetenancyPosture. The README's server-feature-flags section says how to read it besidedisableSignUp.@object-ui/auth's@objectstack/specfloor moves from^17.0.0to^17.3.0. The publishedtypes.d.tsnow referencesAudiencePosture, which 17.0.0 to 17.2.0 do not export, andcheck:spec-floorsnames that exact finding when the old floor is restored (see Evidence). The matchingpnpm-lock.yamlchange is one specifier line, the shape PR feat(plugin-form): derive the inline grid default columns through the spec rulederiveInlineGridColumns(objectui#11345) #11623 used for plugin-form.audienceAdmitsUninvitedSignUp) rather than imported at runtime. This follows thepostureHasOrgWallprecedent in app-shell'suseTenancyPosture.ts, because the login and register pages are in the console's eager closure. A parity test imports the spec'saudiencePermitsSelfRegistrationandAUDIENCE_POSTURESand asserts agreement for every declared posture.auth.register.errors.selfRegistrationClosed, which is the refusal's own copy, plus the existing title and "Already have an account? Sign in" keys.One addition beyond the triage direction: the first-owner window
Triage's direction (comment 6010280240) names two cases that keep the generic sign-up under
invite_only: anopenoremail_domainposture, and an invitation redirect. Measured, a third case is load-bearing:decideAudienceAdmissionadmits a bootstrap creation under every posture, with the comment "a fresh install must never lock its operator out"./setup: nothing inapps/console/srcor app-shell navigates there.Without the window, an unseeded fresh deployment on the default posture would land its operator on a login page with no way to create the first account. So under a closed posture the pages ask
GET /api/v1/auth/bootstrap-statusthroughuseBootstrapStatusfromcomponents/setupEntry.ts(reused, not edited), and keep "Sign up" whilehasOwneris false. The probe runs only when the posture is closed and the visitor is not an invitee.open,email_domainand older servers make no extra request, which is pinned. There is no server change and no new key. If the seat prefers the literal direction, removing thecontext.bootstrap === 'fresh'line fromdecideSignUpOffer, together with its two pins, reverts it.Premises measured
features.audiencePostureis emitted bygetPublicConfigin objectstack'sauth-manager.ts(origin/main01e0f71a) and by the published@objectstack/plugin-auth@17.6.0tarball (audiencePosture: audience.posture). The spec's closed vocabulary isAUDIENCE_POSTURES=invite_only,email_domain,open(@objectstack/spec/system, installed 17.6.0). ItsaudiencePermitsSelfRegistrationis true foremail_domainandopenonly.getAuthConfigunwraps the{ success, data }envelope, so the pages readcfg.features.audiencePosture.decideSignUpOfferreturns the pre-change answer and makes no bootstrap probe. This is pinned for both pages.DefaultAcceptInvitationPagebounces a signed-out visitor to/login?redirect=plus the router path/accept-invitation/ID, and/loginalready forwardsredirectto/register. The pages recognise the/accept-invitation/prefix followed by a non-empty id, andAcceptInvitationPage.tsxis not edited. Recognition is an affordance only: a non-invitee who types the URL by hand still meets the server's refusal, which the form renders localized.@object-ui/auth'sdist/types.d.ts, with a new optionalfeatures.audiencePosturemember and a new type import from@objectstack/spec/system, plus the package's spec floor. No locale key and no export was added or removed.Evidence
All results are at branch head
efcf1eaunless another commit is named.apps/console/src/pages/auth/__tests__/signUpFollowsPosture-11691.test.tsx: 16 passed. A realAuthProviderruns over a realcreateAuthClientagainst a stub server. The end-to-end case clicks "Sign up" from/loginwith an invitation redirect, fills the form, and reads the/sign-up/emailrequest body.387c36bin WRAP mode through objectstack'sscripts/ablation-replace.mjs; each restore was proven as blob equal to HEAD with an emptygit diff HEAD.audiencePosture: 'invite-only'turns consoletscred with TS2820 on that value only; the'invite_only'line beside it compiles. With the probe removed, consoletype-checkis green."@objectstack/spec": "^17.0.0"makescheck:spec-floorsreport@object-ui/auth [floor-too-low] packages/auth/dist/types.d.ts references AudiencePosture from @objectstack/spec/system, which @objectstack/spec@17.0.0 does not export. The floor was then restored.efcf1ea.pnpm exec vitest run apps/console/src/pages/auth/ packages/auth/plus the fourApp.*andinternalFormShelltests that mock these pages: 43 files and 367 tests passed.pnpm --filter @object-ui/auth run type-checkexits 0.pnpm --filter @object-ui/console run type-checkexits 0, after building the@object-ui/console^...closure (34 tasks).lintfor both packages reports 0 errors; its warnings are on pre-existing lines only. Type-check and lint ran at8e12c74, whose sources are byte-identical toefcf1ea: that later commit adds only the changeset.efcf1ea, each exit 0:check:new-line-citations(0 new),check:control-bytes,check:changeset-claims,check:pending-changeset-literals,check:i18n-keys,check:i18n-dead-keys,check:test-path-roots,check:readme-exports,check:spec-symbols,check:phantom-deps,check:lockfile-integrity,check:lockfile-dedupe,check:unreferenced-sources,check:installed-pin-claims,check:vi-mock-specifiers,check:spec-floors,check:eager-closure,node scripts/check-changeset-no-major.mjsandnode scripts/check-changeset-presence.mjs.check:readme-exportsneeds@object-ui/cliand@object-ui/plugin-aibuilt, so they were built first.check:eager-closureran on a fresh consolevite build.check-governed-queue-guard.mjs --testanswers NOT GOVERNED for all 9 paths.pnpm lint, the fullpnpm test, andcheck:i18n-drift(noenvalue changed).Acceptance notes
DefaultLoginPageandDefaultRegisterPageread onlydisableSignUp. A throwaway probe, not committed, measured it withaudiencePosture: 'invite_only'anddisableSignUp: false:DefaultLoginPageoffers "Sign up" to/register, andDefaultRegisterPagerenders the full form. Its control leg (disableSignUp: true) hides the link.examples/console-starter/src/App.tsxroutes both pages. This goes to the seat for its own card and is not edited here, because it is a different package with its own verification surface.content/docs/permissions/authentication.mdxsays "The Console's root route uses it to choose between/login(normal) and/setup(first-run owner creation)", but no console code navigates to/setup. Carrier: none.signUpOffer.tsis a new file beside the two pages.packages/auth/package.jsonand the one-linepnpm-lock.yamlchange are there for floor honesty, under objectui#5793's ruling.packages/auth/README.mdis there because AGENTS.md rule 2 asks for docs.signup_enabledversusaudience_postureon the objectstack settings page).Generated by Claude Code