Skip to content

fix(service-settings): audience help and the identity-auth checklist state the email-verification rule as shipped - #20421

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-20413-verification-posture-statements
Sep 28, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-20413-verification-posture-statements

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Part of #20413 — the settings console text (4 locales) and the identity-auth checklist. The plugin-auth boot-diagnostic location of the same stale statement follows in a second round on the card (seat append below).

Clause-②: no

What changed

The auth settings console and the identity-auth platform checklist both said every audience posture other than invite_only forces email verification on. Commit 65352b7d changed that rule. Each location now states the rule the code enforces.

The rule, re-derived from packages/plugins/plugin-auth/src at base 40b315b0:

  • email_domain always forces verification on (resolveEmailVerificationRequirement, audience-posture.ts). An explicit false is refused at entry from any source (assertAudienceConfig).
  • open forces it on unless the deployment declares an explicit false, in either of two ways:
    • emailAndPassword.requireEmailVerification: false in the stack config;
    • OS_AUTH_REQUIRE_EMAIL_VERIFICATION=false. bindAuthSettings in auth-plugin.ts treats an env-sourced value as the deployment's own declaration.
  • Under open, a false stored through the settings console is refused. This is the verificationDeclaredBy: 'console' branch of assertAudienceConfig, and it plays out two ways:
    • Posture and false saved in one pass: the posture change is refused.
    • false saved while open already stands: the stored false is refused.
    • In both cases the standing config keeps ruling.
  • invite_only uses whatever value is declared, and defaults to off.

The code agrees with the card's statement of the rule.

Landing points (strings re-located on this tree by key and by each locale's wording)

File Keys Hits by key / by wording
service-settings/src/manifests/auth.manifest.ts audience group description; audience_posture description 2 / 2
service-settings/src/translations/en.ts groups.audience.description; keys.audience_posture.help 2 / 2
service-settings/src/translations/zh-CN.ts same keys 2 / 2
service-settings/src/translations/es-ES.ts same keys 2 / 2
service-settings/src/translations/ja-JP.ts same keys 2 / 2
docs/qa/platform-checklist/areas/identity-auth.json two items, below

The card calls the posture string help. In the manifest it is actually the specifier's description; only the bundles call it help.

A repo-wide search for each locale's wording found these files and nothing else that repeats the old rule, with one exception listed under Acceptance notes. Three other hits already state the new rule:

  • the spec describe;
  • the generated reference page;
  • content/docs/deployment/self-hosting.mdx.

No *.generated.ts bundle holds these strings.

"No env knob" was measured, and it is false

The checklist said the audience is config-only, with no env knob. In fact, the settings service derives an OS_AUTH_* override for every auth manifest key (envKeyOf, settings-service.types.ts). So the three audience keys have these overrides:

  • OS_AUTH_AUDIENCE_POSTURE
  • OS_AUTH_AUDIENCE_ALLOWED_EMAIL_DOMAINS
  • OS_AUTH_AUDIENCE_SELF_REGISTRATION_PERMISSION_SET

Probe. A one-off probe, not committed, ran the real SettingsService with the shipped authSettingsManifest, set the env as shown, and called getNamespace('auth'):

control, no env:      audience_posture = "invite_only"  source=default locked=false
open + set + off:     audience_posture = "open"         source=env locked=true
                      audience_self_registration_permission_set = "member_default" source=env locked=true
                      require_email_verification = false source=env locked=true
email_domain + list:  audience_posture = "email_domain" source=env locked=true
                      audience_allowed_email_domains = "acme.com" source=env locked=true
off-vocabulary value: audience_posture = "invite_only"  source=default (override logged as IGNORED)

The plugin-auth half is already pinned. In audience-posture-setting.test.ts, the case "open with the OS_AUTH_REQUIRE_EMAIL_VERIFICATION env override OFF is honoured" drives bindAuthSettings with env-sourced audience keys and asserts posture open.

Composition. settings is in the always-on capability slate for every serve preset except minimal.

Not measured: a live stock boot with those variables set. The checklist text says this.

Checklist: two items, beyond the two quoted sentences

The card quotes two knownGaps sentences. The same file states the same old rule in seven more fields across two items. These are fixed here as a bounded in-place fix, which all four conditions allow:

  1. It is the same defect class.
  2. The edit is mechanical, and the rule above fixes its form.
  3. The file is on this claim's declared surface.
  4. It is the same gate, check:platform-checklist.

Left alone, those fields would contradict the rewritten gaps.

  • identity-auth.self-signup-gate, revision 1 to 2:
    • knownGaps[0] now names both doors: stack config, and the settings namespace through the console or env. It says the env door is measured in halves, not yet end to end.
    • knownGaps[1] (the BOUNDARY note).
    • variants[2].
    • source[2].
  • identity-auth.email-verification-loop, revision 1 to 2:
    • fixtures.requires[0].
    • acceptance[5].
    • negative[2]. As written, it would have scored an open boot with the deployment opt-out as a FAIL. That boot correctly advertises false.
    • source[0] and source[1].

Each item gets a new history entry with ref #20413. The earlier entries keep their text. I searched the file for nine old-rule phrases, such as config-only, no env knob and permitting-posture boot advertising. On base 40b315b0 they occur 11 times; at head they occur 0 times outside the two new history entries.

Verification

Every run below is at head a22b90fc.

Build. pnpm --filter '@objectstack/service-settings^...' build finished with VERDICT command-exit 0, then the package itself was built.

Tests. pnpm --filter @objectstack/service-settings exec vitest run --maxWorkers=2: Test Files 33 passed (33), Tests 584 passed (584). This includes:

  • settings-translation-coverage.test.ts, which checks that zh-CN, ja-JP and es-ES cover settings.auth;
  • auth.manifest.test.ts.

Typecheck. pnpm --filter @objectstack/service-settings run typecheck (tsc --noEmit) exits 0.

Derived gates. node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands derived 57 commands, and all 57 were run. Reconciled with --ran and exit codes: 57 derived, 56 run, 1 NOT-MEASURED.

  • NOT MEASURED: pnpm check:dual-build-cjs-loads. It exited 3 (PREREQUISITE NOT MET) because it needs every package's dist/. Declared narrowing: I applied the gate's two properties to the one package the diff touches.
    • node --check dist/index.cjs exits 0.
    • require('./dist/index.cjs') loads (46 exports, new text present).
    • The diff only changes string literals.
  • check-plugin-teardown-shape.mjs --self-test first exited 3, because its pinned fixture commit was outside the shallow clone. After I fetched that one commit at depth 1, it passed 48 cases.
  • pnpm check:platform-checklist: OK, 15 areas, 266 items.
  • pnpm check:nul-bytes: OK.

Roster gates. The derivation said the rosters of three gates live under packages/, so their silence is not evidence either way. I ran them anyway, and all three exit 0: check:authz-resolver, check:error-code-casing, check:filter-alias-parity.

Translation gates. No gate script reads these translation files. A search of scripts/ and .github/ for service-settings/src/translations and settingsBuiltinTranslations finds 0 hits. As a control, the same search surface does find service-automation. The only gate on these files is the package's coverage test above.

Acceptance notes

  • Stale boot report text. packages/plugins/plugin-auth/src/boot-sign-in-reachability.ts line 478 still says an open or email_domain posture "forces email verification on the INVITED login too". Line 492 of the same message already names the open opt-out. plugin-auth source is outside this claim, so this goes to the PM as a finding. The phrase is pinned in boot-sign-in-reachability.test.ts.
  • Stale comment. A comment in auth-manager.ts, near line 2295, still says a self-registration posture forces verification on. It is a comment only.

Changeset

.changeset/20413-verification-posture-statements.md: @objectstack/service-settings patch, because the strings ship in that package. The docs/qa file is not published.

Seat append (domain:services seat #6021, session_017B6YKCGu8CTY2KBWgwaHAs)


Generated by Claude Code

…shipped

email_domain always forces email verification on; open forces it on unless
the deployment turns it off (OS_AUTH_REQUIRE_EMAIL_VERIFICATION=false, or
emailAndPassword.requireEmailVerification: false in the stack config); a
false stored through the console is refused under open. The audience group
description and the audience_posture help said every non-invite_only
posture forces it, in the manifest and all four locales.

Claude-Session: https://claude.ai/code/session_017B6YKCGu8CTY2KBWgwaHAs
Co-authored-by: Claude <noreply@anthropic.com>
…e audience env door as shipped

self-signup-gate and email-verification-loop said every permitting posture
forces email verification, and that the audience is config-only with no env
knob. email_domain always forces it; open forces it unless the deployment
declares it off, and a console-stored false is refused under open. The
audience is also declared through the auth settings namespace (console and
the OS_AUTH_AUDIENCE_* env overrides). Both items bump to revision 2 with a
history entry; steps, oracles and the unaffected clauses are unchanged.

Claude-Session: https://claude.ai/code/session_017B6YKCGu8CTY2KBWgwaHAs
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation tooling labels Sep 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)), so this run has no opinion about the docs.

What this run could not see

Coarse fallback — 8 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 50e273fd7e933433182d6d89e3968d1af4be7b94 → packageMentionDocs.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

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

Inputs read: card #20413 (body + all 4 comments: unlock 5866972701, claim 5867222316, os-dev-report 5867903998, seat ACCEPT 5867962277), PR #20421 (body, 7-file list, net diff vs main at 50e273fd), the head's check-runs (three readings, last at 2026-09-28T10:22:47Z), and the enforcing code on origin/main (packages/plugins/plugin-auth/src/audience-posture.ts, auth-plugin.ts bindAuthSettings, auth-manager.ts applyConfigPatch, audience-posture-setting.test.ts; packages/services/service-settings/src/settings-service.ts and settings-service.types.ts envKeyOf; packages/spec/src/system/auth-config.zod.ts). The review ref refs/review/pr-20421 resolves to the head sha exactly. Nothing was built, run or re-run.

① Derived judgments

The diff is 7 files, +40/−23, string literals and JSON prose only. Every accept-set and public-surface implication, each judged:

  1. auth.manifest.ts — audience group description and the audience_posture specifier description (the manifest carries the posture string as description; the bundles carry it as help — the card's help naming was wrong, the dev's correction is RIGHT, verified at head lines 92 and 106). Keys, options table, defaults, type, namespace: 'auth' all unchanged, so the accept set of the settings console and of every OS_AUTH_* override derived from these keys does not move. Right: text-only, no accept-set change.
  2. The four locale bundles (en, zh-CN, es-ES, ja-JP), keys groups.audience.description and keys.audience_posture.help — same two keys in each, no key added or dropped, so the coverage invariant (settings-translation-coverage.test.ts) is undisturbed; en strings are byte-identical to the manifest's. The zh-CN, es-ES and ja-JP wordings each state the three clauses below faithfully (I read all three). Right.
  3. Accuracy of the rule as now stated, measured against origin/main:
    • "email_domain always forces email verification on" — resolveEmailVerificationRequirement returns true for email_domain; assertAudienceConfig refuses an explicit false under email_domain regardless of declarant (pinned by "email_domain with the env override OFF is still refused"). Right.
    • "open forces it on unless the deployment turns it off (OS_AUTH_REQUIRE_EMAIL_VERIFICATION=false, or emailAndPassword.requireEmailVerification: false in the stack config)" — declared !== false for open; the constructor passes verificationDeclaredBy: 'deployment' (auth-manager.ts:1392); bindAuthSettings maps sources.require_email_verification === 'env' to 'deployment' (auth-plugin.ts:1706-1707); pinned by "open with the OS_AUTH_REQUIRE_EMAIL_VERIFICATION env override OFF is honoured". Right.
    • "a false saved in this console is refused under open" / "a value saved in this console cannot" — the 'console' branch of assertAudienceConfig; applyConfigPatch carries requireEmailVerificationFrom: 'console' for any non-env explicit source and re-judges the merged result whenever audience or emailAndPassword is in the patch (auth-manager.ts:4208-4222). Both orderings end refused with the standing config ruling. Right.
    • "Both email_domain and open require the self-registration permission set below" — audiencePermitsSelfRegistration branch of assertAudienceConfig. Right.
  4. docs/qa/platform-checklist/areas/identity-auth.json — unpublished; two items (self-signup-gate, email-verification-loop) revision 1 to 2, each with a new history entry whose revision equals the item's (the validator's rule at scripts/check-platform-checklist.mjs:2741-2751); symbol anchors in source unchanged, only their parentheticals. Nine fields restated. The "audience is config-only, no env knob" claim the card marked unverified by the PM is now measured false by me as well: envKeyOf('auth','audience_posture') yields OS_AUTH_AUDIENCE_POSTURE for every manifest key; the settings service resolves an env value as source: 'env', locked: true (settings-service.ts:1229-1233) and rejects an off-options value as IGNORED (:1128); bindAuthSettings reads audience_posture etc. by explicitness, and audience-posture-setting.test.ts:257 drives env-sourced audience keys through it. The checklist says the live stock boot with those variables is not measured — honest. My grep of the head file finds 0 old-rule phrases outside the two new history entries; base held them in 11 places. Right.
  5. No other public surface moves. Repo-wide grep on origin/main for the five old strings hits only the 5 touched source files (no *.generated.ts, no reference page copy); the spec posture describe, content/docs/references/system/auth-config.mdx and content/docs/deployment/self-hosting.mdx:580 already state the shipped rule. No packages/spec, no plugin-auth source, no content/docs/releases/, no governed surface (docs/qa/** is not on the Prime Directive feat: Comprehensive CRM example demonstrating all ObjectStack protocol features #14 register). Right.

② Semver level

The declaration is Clause-②: no.

.changeset/20413-verification-posture-statements.md: "@objectstack/service-settings": patch, Clause-②: no on its own line, no arm. Right. The package publishes (publishConfig present, no private), and the diff changes strings that ship in its bundle, so skip-changeset would be wrong; nothing in the accept set widens or narrows and no export, key, option or default moves, so minor is not owed and no is the correct declaration. The changeset body carries no migration (none is owed), no ADR-0087 marker (not breaking), no model identifier, and its (#20413) title suffix follows the repo's existing changeset convention. docs/qa carries no changeset — right, it is not published. Check Changeset on the head: success.

③ Boundary flags

  • open_questions: empty — recorded; none to answer.
  • Deviation: checklist scope beyond the two quoted knownGaps sentences (7 more fields, 2 items). Judged right: same file on the declared claim surface, same defect (the pre-feat(plugin-auth): the open audience posture honours a deployment's email-verification opt-out (#20389) #20406 rule), a mechanical restatement fixed in form by the shipped rule, same gate (check:platform-checklist); leaving them would have left the file self-contradicting. The seat's ACCEPT (5867962277 item 3) amended the claim surface accordingly. Verified by diff and by grep of base vs head.
  • Deviation: card said help, manifest carries description. Verified at head; card wording corrected, no action owed.
  • Deviation: check:dual-build-cjs-loads NOT-MEASURED with declared narrowing. Acceptable: the diff is string literals; Build Core on the head is success and stands as the build verdict.
  • Deviation: one pinned fixture commit fetched at depth 1 for a checker self-test. Additive object-store fetch, no ref moved; no concern.
  • Deviation: commit trailers use the AGENTS.md model-free pair. Verified on all three branch commits (d3eb775e, 380efb09, a22b90fc): Claude-Session + Co-authored-by: Claude, no model identifier anywhere in commits, changeset, PR title or body. Right per AGENTS.md.
  • Out-of-scope (b): boot-sign-in-reachability.ts remedy prose. Verified on origin/main lines 478-479 ("forces email verification on the INVITED login too") against 492-493 of the same message ("unless an 'open' deployment has itself declared it off") — the message contradicts itself, is shipped runtime text, and is outside this claim's surface. The seat merged it into Stale "every non-invite_only posture forces email verification" statements after #20389: settings console text (4 locales) and the identity-auth platform checklist #20413 as round 2 (5867962277 item 6). Escalated correctly; no action on this PR.
  • Out-of-scope: auth-manager.ts comment near line 2295. Verified stale ("a posture that permits self-registration FORCES requireEmailVerification on"); comment only; rides round 2. Right.
  • PR closes nothing. First line Part of #20413 (seat's edit), no closing keyword elsewhere; Part-of PR must not also close its card and The card this PR closes must claim this branch both success. Round 2 is owed before the card closes.
  • Observation, not a defect in this diff (plugin-auth behaviour landed by feat(plugin-auth): the open audience posture honours a deployment's email-verification opt-out (#20389) #20406, outside this claim): when posture open already stands and the console then stores require_email_verification: false, the refusal is thrown from the MAIN settings patch and lands in the outer catch at warn ("Auth: failed to apply auth settings: …"), dropping the pass's sibling auth settings — the test file's own comment names this shape. The new text's word "refused" is accurate either way; the seat may fold the log-level/sibling-drop question into round 2 or leave it.
  • Check-runs on a22b90fc at 2026-09-28T10:22:47Z: 34 distinct checks — 28 success, 5 skipped, 0 failure; Lint & Repo Gates (a required context) was still in_progress at my last reading. All six other required contexts (TypeScript Type Check, Test Core and its 6 shards, Dogfood Regression Gate and its 3 shards, Build Core, Temporal Conformance (live PG + MySQL), Governed Surface Queue Guard) are success. This is an honest reading of the gate, not a verdict on it: readiness waits for that one context to conclude green.

Implemented-by: claude/issue-20413-verification-posture-statements
Reviewed-by: session_017B6YKCGu8CTY2KBWgwaHAs

VERDICT: PASS

Rendered by an isolated contract-review subagent and adopted by the domain:services seat (#6021, session_017B6YKCGu8CTY2KBWgwaHAs) at 2026-09-28T10:24Z after a transcript check: 70 harness model stamps, all at CONTRACT_REVIEW_TIER, zero fallbacks; 39 Bash calls, zero write calls. Seat note on the last observation: that refusal-drops-siblings shape is exactly #20412's scope (in flight, auth-plugin.ts), not round 2's.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 28, 2026 10:26
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 28, 2026
Merged via the queue into main with commit 24b7085 Sep 28, 2026
43 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20413-verification-posture-statements branch September 28, 2026 10:58
veigajoao pushed a commit to veigajoao/objectstack that referenced this pull request Sep 29, 2026
…-verification rule as shipped (objectstack-ai#20434)

Fixes objectstack-ai#20413

Clause-②: no

Round 2 of the card, location 4: the plugin-auth boot report. Round 1
(PR objectstack-ai#20421, landed as `24b70859`) covered locations 1–3. This round
completes the card. It changes text, comments and tests only. No
admission decision, verification default or accepted value moves.

## What changed

The `no_sign_in_account_at_boot` report tells an operator to recover a
locked-out deployment by inviting one address. Its advice said an
`'open'` or `'email_domain'` posture "forces email verification on the
INVITED login too". Later, in remedy (b), the same message said
verification is forced "unless an 'open' deployment has itself declared
it off". The first sentence stopped being true when `open` began
honouring a deployment's opt-out (`65352b7d`, objectstack-ai#20406), so the message
contradicted itself.

**The rule, re-derived from `packages/plugins/plugin-auth/src` at
`24b70859`:**

- `resolveEmailVerificationRequirement` (audience-posture.ts), the one
resolver the better-auth wiring and `getPublicConfig()` both read:
  - `email_domain` is always on;
  - `open` is on unless the declared value is `false`;
  - `invite_only` uses the declared value, off by default.
- `assertAudienceConfig` refuses an explicit `false` under
`email_domain` from any source. Under `open` it refuses a `false` whose
declarant is `console`.
- `applyConfigPatch` (auth-manager.ts) marks a patch as the deployment's
unless its caller passes `requireEmailVerificationFrom: 'console'`.
- The invitation carve-out in `decideAudienceAdmission` returns only
`admit` and `grantPermissionSet`, so it cannot exempt anyone from
verification. The only row born `emailVerified: true` is the
walled-owner operator stamp, and an invitation-admitted creation never
gets it.

**The message now:**

- states the rule once, in the invitation paragraph:
- the INVITED login meets the same email-verification rule as any
sign-up;
  - `email_domain` is always ON;
- `open` is ON unless the DEPLOYMENT declared it off
(`emailAndPassword.requireEmailVerification: false` or
`OS_AUTH_REQUIRE_EMAIL_VERIFICATION=false`), and a `false` stored only
through the settings console is refused there;
  - `invite_only` follows that declaration and is OFF by default.
- keeps the scoped no-mail-transport rider and the advice order ("close
the posture back to 'invite_only' BEFORE that person registers"). It
adds "and leave verification at its default OFF", because under
`invite_only` a declared `true` would still apply.
- in remedy (b), refers back to that rule ("wherever it is ON") instead
of restating it.

The advice still matches `content/docs/deployment/self-hosting.mdx`
("Email verification under each posture"), which the module docblock
names as this message's long form. That page already stated the rule
correctly.

**Files:**

- `packages/plugins/plugin-auth/src/boot-sign-in-reachability.ts`: the
remedy string, plus the file docblock's invitation bullet (the old
"third bullet below applies to the INVITED login too" sentence).
- `packages/plugins/plugin-auth/src/boot-sign-in-reachability.test.ts`:
- the pin at the old line 164 is flipped. It now asserts the new text's
substance: the invited-login rule, `email_domain` always ON, the `open`
conditional, the console refusal, and the rider's new wording.
  - two negative pins are added, one per old unconditional spelling.
- a new `objectstack-ai#20389` describe drives each clause against the manager: `open`
+ the deployment's `false` wires OFF; the same `false` from the console
is refused and stays ON, with the deployment patch as control;
`email_domain` + `false` refuses the boot, with a control; `invite_only`
is OFF by default and ON when declared.
- three titles and comments that repeated the old quantifier now say
"with nothing declared".
  - `managerWith` takes an optional deployment `emailAndPassword` block.
- `packages/plugins/plugin-auth/src/auth-manager.ts`: one comment near
the old line 2295 (the objectstack-ai#15587 uniqueness-refusal block), restated as "on
by default".
-
`packages/plugins/plugin-auth/src/signup-existing-address-refusal.test.ts`:
one docblock sentence (old line 30), restated the same way.
- `.changeset/20413-boot-report-verification-text.md`:
`@objectstack/plugin-auth` `patch`. The boot report prose ships.

Stale-phrase counts across `packages/plugins/plugin-auth/src`, BASE
`24b70859` → HEAD `7898a47d`: "INVITED login too" 2 → 0; "every posture
other than 'invite_only' ('open'," 1 → 0; "self-registration FORCES
`requireEmailVerification` on (see" 1 → 0; "self-registration-permitting
posture FORCES `requireEmailVerification`" 1 → 0; "third bullet below
applies to the INVITED login too" 1 → 0.

## Verification (all at HEAD `7898a47d`)

- **Build:** `pnpm --workspace-concurrency=2 --filter
'@objectstack/plugin-auth^...' build`, run through the verify lock:
`VERDICT command-exit 0`. Then `pnpm --filter @objectstack/plugin-auth
build`: check-dts-emitted 2/2.
- **Targeted tests:** `pnpm --filter @objectstack/plugin-auth exec
vitest run --maxWorkers=2 src/boot-sign-in-reachability.test.ts
src/signup-existing-address-refusal.test.ts` gave `Test Files 2 passed
(2)` / `Tests 59 passed (59)`.
- **Package tests:** `pnpm --filter @objectstack/plugin-auth exec vitest
run --maxWorkers=2` gave `Test Files 114 passed (114)` / `Tests 2458
passed (2458)`.
- **Typecheck:** `pnpm --filter @objectstack/plugin-auth run typecheck`
exited 0. It runs `tsc --noEmit`, the examples project, and
`check:test-typecheck`, which reported "OK … 10 file(s) / 94 error(s) …
held in test-typecheck-debt.json".
- **Reverse verification.** The fix was committed first. Each leg ran
through `scripts/ablation-replace.mjs` in WRAP mode, with a driver trap
that restores from HEAD by absolute path. The test imports the module by
relative path, so it reads `src/` and no rebuild was needed between
legs.
- Leg A put the old sentence back in place of the new one. The anchor
went 1 → 0 and the blob went `b0be40fa` → `d1b521c9`. Result: `Tests 1
failed | 50 passed (51)`, failing `[objectstack-ai#15588/F1] the no-mail-transport
rider is SCOPED to the default posture` with "expected '[auth]
no_sign_in_account_at_boot: th…' to contain 'under the default
\'invite_only\' pos…'".
- Leg B added the old sentence beside the new text. Result: the same
single test red, on the negative pin "not to match /an 'open' or
'email_domain' posture …/i".
- Leg C added the old remedy-(b) quantifier. Result: the same single
test red, on "not to match /every posture other than 'invite_onl…/i".
- After all three legs, the blob equals the HEAD blob (`b0be40fa`) and
`git diff HEAD` is empty. The direction observed was red, as expected.
- **Lint, narrowed to the 4 touched `.ts` files:** `eslint
--no-inline-config --format json` linted 4 files with 0 errors and 0
warnings. `eslint.config.mjs` never enables type-aware linting (no
`parserOptions.project`, no typed rules), so this diff cannot change a
verdict on an untouched file. The repo-wide run belongs to CI.
- **Derived gates:** `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` derived 64 commands over these 5
paths. `--ran` reconciled "64 derived, 62 run, 2 NOT-MEASURED, 0 UNRUN".
All 62 that ran exited 0.
- NOT MEASURED: `pnpm check:dual-build-cjs-loads` exited 3 with
PREREQUISITE NOT MET, because it needs every package's `dist/` and this
tree built only the plugin-auth closure. Declared narrowing, applied to
the one touched package: `node --check dist/index.js` exits 0, and
`require('./dist/index.js')` loads 143 exports. The new text is present
in `dist/index.js` and `dist/index.mjs` (1 each), and the old phrase in
neither (0 each).
- NOT MEASURED: `pnpm check:type-check-debt` exited 3 with PREREQUISITE
NOT MET, because `@objectstack/runtime`, `service-cluster` and
`service-job` have no built types here. Declared narrowing: the ledger's
DEBT keys are `cloud-connection`, `hono`, `observability` and
`spec-monorepo`, none of which depends on plugin-auth, and TEST_DEBT is
empty. The only non-comment source change is the content of one string
literal, so no type moves.
- `check:nul-bytes`: OK. Control-byte self-scan over the 5 files: no
hits.

## Acceptance notes

Sweep, at `24b70859`: lines naming email verification
(`email.?verification|requireEmailVerification|EMAIL_NOT_VERIFIED|REQUIRE_EMAIL_VERIFICATION`)
that also carry a forcing or quantifier word (`forc|other than
invite_only|every posture|permitting posture|widen`). That gave 51 lines
in 19 files, and every hit outside round 1's surface was read in
context. In the files this round owns, all hits are fixed.

**Stale or incomplete outside this round's surface. All are comments or
frozen history; none is edited here.**

- `packages/plugins/plugin-auth/src/audience-gate-test-support.ts:17`:
"`open` (and `email_domain`) force `requireEmailVerification` on". This
is a docblock explaining why fixtures use the invitation lane. It is
true for the undeclared fixture config but omits `open`'s opt-out. No
carrier.
-
`packages/plugins/plugin-auth/src/admin-remove-user-gate-ordering.test.ts:135`:
"`open` would force email verification on". The same shape, in a test
comment. No carrier.
- `packages/verify/src/harness.ts:778`: "`open` and `email_domain` force
`requireEmailVerification` on". The same shape, in a comment. No
carrier.
-
`packages/spec/src/migrations/entries/semantic/18.audience-posture-default-invite-only.ts:19`,
and its generated mirror
`packages/spec/src/migrations/registry.ts:6270`: "accepts forced email
verification". This is the v18 migration's reason text, accurate for
that release. It is `packages/spec` history, not live advice.

**Read and left, because each is correct:** `audience-posture.ts:221`
(it says "unless noted", then notes the `open` exception);
`auth-manager.ts:1639` (it carries the objectstack-ai#20389 exception); the runtime
log at `auth-manager.ts:4818` ("… when email verification is forced on"
is a sufficient condition, not a rule statement);
`packages/spec/src/system/auth-config.zod.ts` lines 408–419 and 588;
`content/docs/deployment/self-hosting.mdx:580` and its posture section;
`content/docs/deployment/environment-variables.mdx:84`; and the
`audience-posture*.test.ts` assertions, which pin the refusal messages
as they stand.

`auth-plugin.ts` has no hit, so nothing reaches the file objectstack-ai#20412 holds.

---

_Generated by [Claude
Code](https://claude.ai/code/session_017B6YKCGu8CTY2KBWgwaHAs)_

Co-authored-by: Claude <noreply@anthropic.com>
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/s tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants