Skip to content

fix(#4509): three auth-gate disconnects close — email_template bridges, job and validation close their doors - #4558

Merged
os-zhuang merged 6 commits into
mainfrom
claude/auth-gate-disconnect-issues-6wz8ew
Aug 2, 2026
Merged

os-zhuang merged 6 commits into
mainfrom
claude/auth-gate-disconnect-issues-6wz8ew

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Closes items 1–3 of #4509. Item 4 (nav action dispatch) is objectstack-ai/objectui#3180.

Each of these is an authorization gate opening onto empty ground: an author writes metadata, the platform accepts it, and nothing ever reads it. Per ADR-0049 enforce-or-remove, each is resolved — never left standing. The route differs per item, and that is the interesting part: enforce where the feature exists and only the wiring is missing, remove where the shape cannot carry the feature at all.


1. email_template → bridge built (the security-shaped one)

sendTemplate resolves (name, locale) against sys_email_template rows, whose only writers were the built-in auth templates plus a code-constructed EmailServicePluginOptions.templates that no bootstrapper ever passed. Meanwhile every door an author can use — a stack's emailTemplates:, an *.email-template.ts file, Studio's metadata-admin list, PUT /meta — parked items in a metadata store nothing read back. An admin could "fix" the password-reset email in Studio, get a success toast, and watch users keep receiving the built-in copy: ADR-0078 false compliance on authentication mail.

bootstrapDeclaredEmailTemplates materializes declared templates into sys_email_template at boot, validating each through EmailTemplateDefinitionSchema.parse() and projecting with mapTemplateToRow — the same mapping the built-in seeder uses, extracted and shared so the two doors cannot drift apart. Because email_template is allowRuntimeCreate: true (unlike webhook), the plugin also subscribes to metadata changes and re-materializes the changed item, so a Studio save takes effect without a restart; a withdrawn template deactivates its rows rather than losing them.

Three breaks sat on this path, not one, and closing any two would still have shipped a template that never sent:

  1. @objectstack/objectql never registered a manifest's emailTemplates: into the metadata registry at all — the key was simply missing from the generic ingestion list, so the bridge's own source was empty.
  2. The built-in seeder left managed_by at the column's 'admin' default, making platform templates masquerade as admin-authored. Since the bridge refuses to overwrite admin rows, a built-in would have permanently outranked the template an app declared.
  3. Nothing materialized declared metadata into rows.

The first two were found by the ADR-0054 proof, not by the unit tests — which is the argument for having written it.

Seed-not-clobber mirrors sys_webhook (#3489): sys_email_template gains managed_by / customized, declared rows re-seed as package, and an admin-created or admin-edited row is never overwritten. Separate axis from is_system, which keeps its meaning for built-ins.

2. job → gate closed (cannot be bridged)

A runtime-created job could never be scheduled. JobSchema.handler names a function in the compiled bundle's function table — the schema says so, and AppPlugin sources jobs from bundle.jobs alone, resolving handlers through collectBundleFunctions(bundle). The runtime writer does not have the bundle and cannot name a function inside it, so the missing piece is a handler-binding design, not an ingestion path.

allowRuntimeCreate: false and allowOrgOverride: false (the latter goes beyond the issue's literal text — an org-overridden job is unreachable for exactly the same reason). A rationale block modeled on the agent entry states what re-opening would require. The kind stays registered: its file loader is genuinely consumed, so it still passes the ADR-0088 admission test, and *.job.ts / defineStack({ jobs }) remain fully enforced.

3. validation → kind retired (ADR-0088)

A rule authored as its own artifact bound to nothing. ValidationRuleSchema carries no object / objectName key, and all six variants are strictObject — so an author could not supply one either. No merge step existed; the only code expecting such a key was a reference-tracker row scanning a field the schema would have stripped. The engine evaluates exactly one shape: the object's own validations[].

So a rule created through the standalone door saved cleanly, reported success, and intercepted nothing — including a state_machine rule, which ADR-0020 routes through this same vocabulary. An author could believe they had locked down record state transitions and have changed nothing.

The kind fails the ADR-0088 admission test on its first clause: a rule has no independent lifecycle, because it only means something against an object. Removed: registry entry (with its file patterns), MetadataTypeSchema member, metadata-core lockstep enum member, schema-map entry, create seed, Studio's Validations nav item + hand-crafted form + its four locale strings, and the dangling reference row.

The rule vocabulary is untouched — ValidationRuleSchema and all six variants are unchanged and fully live, and the engine's evaluation path is not modified. The ledger keeps governing the schema through the gate's SPEC_ONLY_SCHEMAS override (alongside webhook and query), because an ungoverned live schema is how the next drift would hide. Stored standalone rows are left alone: never evaluated, so nothing changes behaviorally.


Verification

  • ADR-0054 runtime proof (email-template-materialization, bound on email_template.subject): boots a real stack, authors a template that overrides a built-in auth template, and asserts the authored wording reaches the transport — both ends of the graph the issue found disconnected.
  • 16 new unit tests for the bridge (mapping, two-locale rows, idempotency, propagate-to-pristine, seed-not-clobber, admin collision, pristine adoption, invalid-input skip, no-op, metadata-service fallback, live upsert, withdrawal). Red-proofed: removing the customized guard fails exactly the seed-not-clobber test.
  • Full workspace: pnpm build + pnpm test green (132 tasks; dogfood 440 passed).
  • All gates: check:liveness, check:empty-state, check:variant-docs, check:strictness-ledger, check:generated, check:i18n — green.
  • Ledgers updated: email_template flips 13 dead → 21 live with the bound proof; job and validation record their closures; app.json re-pins the objectui evidence. Stale evidence line numbers restamped in job.json (~25 lines drifted) and validation.json (~220) — the gate resolves paths, not line numbers, which is the rot verifiedAt exists to catch.

Reviewer's attention

  • job's allowOrgOverride: false is broader than the issue asked for; the reasoning is in the rationale block, and it is easy to drop if you disagree.
  • Delete handling in the email bridge deactivates rather than deletes, and only rows it owns — metadata delete events carry no locale, so a name-wide sweep is the only option and destroying admin-tuned rows would be the wrong one.
  • packages/runtime/src/domains/packages.ts's emailTemplates export exclusion is comment-only here; whether package-installed templates reach the registry is a separate question from the authoring doors this closes.
  • Studio's email-template nav item is deliberately unchanged — metadata is now genuinely the authoring surface, so it points at the right place.

Generated by Claude Code

claude added 5 commits August 2, 2026 02:15
…ail_template (#4509)

Authoring an `email_template` was a silent no-op. `sendTemplate` resolves
`(name, locale)` against sys_email_template ROWS, whose only writers were the
built-in auth templates plus a code-constructed `EmailServicePluginOptions.
templates` that no bootstrapper passed — while every door an author can use
(stack `emailTemplates:`, `*.email-template.ts`, Studio, PUT /meta) parked items
in a metadata store nothing read back. An admin could "fix" the password-reset
mail in Studio and users kept receiving the built-in copy: ADR-0078 false
compliance on AUTH mail. Webhook #3461's shape, closed the same way (ADR-0049
enforce-or-remove, route: enforce).

`bootstrapDeclaredEmailTemplates` materializes declared items at boot, validating
each through EmailTemplateDefinitionSchema.parse() and projecting with
`mapTemplateToRow` — the SAME mapping the built-in seeder uses, extracted and
shared so the two doors cannot drift. Because email_template is
allowRuntimeCreate:true (unlike webhook), the plugin also subscribes to metadata
changes and re-materializes the changed item, so a Studio save takes effect
without a restart; a withdrawn template deactivates its rows rather than losing
them.

Three breaks sat on this path, and closing any two would still have shipped a
template that never sent:
- objectql never registered a manifest's `emailTemplates:` into the registry at
  all — the key was missing from the generic ingestion list, so the bridge's own
  source was empty.
- Built-in seeds left `managed_by` at the column's 'admin' default, masquerading
  as admin-authored; since the bridge refuses to overwrite admin rows, a built-in
  would permanently outrank the template an app declared. Built-ins now stamp
  'platform'.
- Nothing materialized declared metadata into rows.

Seed-not-clobber mirrors sys_webhook (#3489): sys_email_template gains
managed_by/customized, declared rows re-seed as 'package', and an admin-created
or admin-edited row is never overwritten. Separate axis from `is_system`, which
keeps its meaning for built-ins.

Ledger email_template flips 13 dead → fully live, with an ADR-0054 proof bound on
`subject`: it boots a real stack, authors a template overriding a built-in auth
template, and asserts the AUTHORED wording reaches the transport.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E5CYr5SDwe85gH2Jr5KSgu
…fact (#4509)

A `job` created at runtime could never be scheduled. `JobSchema.handler` names a
function in the compiled bundle's function table — the schema says so, and the
scheduler is built that way: AppPlugin sources jobs from `bundle.jobs` alone and
resolves handlers through `collectBundleFunctions(bundle)`. Yet the type was
registered allowRuntimeCreate:true (and allowOrgOverride:true), so a job authored
in Studio or via PUT /meta parsed, saved, reported success, and never ran.

Unlike the sibling disconnects in this batch, this one cannot be bridged: the
runtime writer does not have the bundle and cannot name a function inside it. The
missing piece is a handler-binding design, not an ingestion path. Per ADR-0049
enforce-or-remove the honest move is to close the door — both flags now false,
with a rationale block modeled on the `agent` entry stating what re-opening would
require.

`job` stays a first-class authorable type: `*.job.ts` and `defineStack({ jobs })`
are the supported doors and are fully enforced, so the kind still passes the
ADR-0088 admission test and stays registered.

Also restamps job.json's evidence lines, which pointed at app-plugin.ts:767-791
and had drifted ~25 lines — the gate resolves paths, not line numbers, which is
exactly the rot `verifiedAt` exists to catch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E5CYr5SDwe85gH2Jr5KSgu
…venance columns (#4509)

Follow-up to the materializer bridge: `managed_by` / `customized` on
sys_email_template are new translatable field labels, so the nine locale bundles
had drifted from the schema. `node scripts/check-i18n-bundles.mjs --write`
(merge mode — no existing translation overwritten).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E5CYr5SDwe85gH2Jr5KSgu
…DR-0088)

A validation rule authored as its own artifact bound to nothing and gated no
write. ValidationRuleSchema carries no object-binding key — no `object`, no
`objectName` — and all six variants are strictObject, so an author could not
supply one either. No merge step existed; the only code expecting such a key was
a reference-tracker row scanning a field the schema would have stripped.
Meanwhile the engine evaluates exactly one shape: the object's own
`validations[]`, on insert and on every matched update row.

So a rule created through the standalone door (*.validation.ts, or Studio's
Validations list) parsed, saved, reported success, and intercepted nothing —
including a state_machine rule, which ADR-0020 routes through this same
vocabulary. An author could believe they had locked down record state
transitions and have changed nothing.

Under ADR-0088 the kind fails the admission test on its first clause: a rule has
no independent lifecycle, because it only means something against an object. And
unlike the sibling disconnects in this batch it could not be bridged into one —
the shape has nowhere to name its object. Enforce-or-remove picks ENFORCE where
the feature exists and only wiring is missing (email_template), REMOVE where the
shape cannot carry the feature.

The rule VOCABULARY is untouched: ValidationRuleSchema and all six variants are
unchanged and fully live, and the engine's evaluation path is not modified. The
ledger keeps governing the schema through the gate's SPEC_ONLY_SCHEMAS override
(alongside webhook and query) — an ungoverned live schema is how the next drift
would hide.

Removed: registry entry (with its file patterns), MetadataTypeSchema member,
metadata-core lockstep enum member, schema-map entry, create seed, Studio nav
item + hand-crafted form + its four locale strings, and the dangling
reference-tracker row. Stored standalone rows are left alone — never evaluated,
so nothing changes behaviorally.

Also restamps validation.json's engine evidence lines (drifted ~220 lines) and
adds the ADR-0088 addendum recording the retirement.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E5CYr5SDwe85gH2Jr5KSgu
app.json's type note carried the one gap the AppSchema navigation walk found by
hand: an `action` item rendered and gated like any other while its click went to
an `onAction` prop no shipped shell passed. objectui @e8bec83 wires it — the
sidebar resolves the action name against metadata and dispatches through the
console action runtime, and a shell that still passes no handler now hides
action items instead of rendering them dead.

Re-pins the objectui evidence (the old pin @940ba24 predates the fix) and dates
the row.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E5CYr5SDwe85gH2Jr5KSgu
@vercel

vercel Bot commented Aug 2, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 2, 2026 8:28am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation dependencies Pull requests that update a dependency file tests tooling labels Aug 2, 2026
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 7 package(s): @objectstack/metadata-core, @objectstack/metadata-protocol, @objectstack/objectql, @objectstack/platform-objects, @objectstack/plugin-email, @objectstack/dogfood, @objectstack/spec.

111 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/plugin-email, @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/metadata-core, @objectstack/metadata-protocol, @objectstack/objectql, packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via packages/objectql, @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/migration-from-objectql.mdx (via @objectstack/objectql)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/deployment/vercel.mdx (via @objectstack/objectql)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/metadata-protocol, @objectstack/objectql, @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/permissions/authentication.mdx (via @objectstack/objectql)
  • content/docs/permissions/authorization.mdx (via packages/qa/dogfood, @objectstack/spec)
  • content/docs/permissions/delegated-administration.mdx (via packages/qa/dogfood)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/metadata-core, @objectstack/objectql, @objectstack/platform-objects, @objectstack/plugin-email, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/objectql, @objectstack/plugin-email, @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/runtime-capabilities.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via packages/objectql, @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/metadata-core, @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/metadata-protocol, @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/platform-objects, @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

CI's slot-lookup ratchet caught the bridge's `ctx.getService('metadata')`:
email-plugin.ts is grandfathered for its three existing erasures, and a new
untyped lookup pushed it to four. Pass the slot's contract type
(`IMetadataService`) as #4251 asks — which also gives the subscribe/get calls a
real type instead of `any`.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E5CYr5SDwe85gH2Jr5KSgu
@os-zhuang
os-zhuang marked this pull request as ready for review August 2, 2026 08:36
@os-zhuang
os-zhuang enabled auto-merge August 2, 2026 08:36
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 2, 2026
Merged via the queue into main with commit ce92674 Aug 2, 2026
22 checks passed
@os-zhuang
os-zhuang deleted the claude/auth-gate-disconnect-issues-6wz8ew branch August 2, 2026 08:49
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 3, 2026
…d connect doc.tags (objectstack-ai#4509) (objectstack-ai#4664)

Closes the "顺带的三个小清理候选" section of objectstack-ai#4509 — the part left over after
objectstack-ai#4558 landed the four structural disconnects.

What groups the five retirements is not the type they sit on but WHY they had
to go out in a major rather than after a deprecation cycle: four of the five
carry schema DEFAULTS, and a default materialises at parse time, so the
liveness advisory lint cannot tell a value the author wrote from one the schema
supplied. Marking them would have warned on every mapping and every selector in
existence — which is why the ledger recorded `_authorWarnSkipped` instead of
`authorWarn`. For a key in that state, removal is not the escalation after a
warning; it is the only channel that ever reaches the author. With spec at
17.0.0-rc.1 and pre-mode still open, that channel closes at `changeset pre
exit` and reopens in v18.

Removed (strict deletion + `guidance` prescriptions, ledger rows deleted):

  mapping.extractQuery    promised an export path no exporter implements
  mapping.errorPolicy     error handling belongs to the import REQUEST
  mapping.batchSize       the write path sizes its own batches
  app.contextSelectors[].includeAll
  app.contextSelectors[].placement

`includeAll` is the one worth reading twice: not unread but deliberately
DISOBEYED, and for a security reason. Context selectors are mandatory-scope, so
an "All" row would clear a scope that exists to be scoped — on Studio's package
selector that means listing the platform's own system/cloud kernel packages to a
developer who scoped to their own package. STUDIO_APP shipped authoring
`includeAll: true` against a renderer that ignored it; that authoring site goes
with the key here.

`batchSize` deliberately offers no rename. bulkActionDef/connector/sync/offline
/seed-loader/NoSQL-cursor `batchSize` are all live and enforced, but each is a
different key on a different type sizing its own path. "Removed" plus a familiar
name one line away is exactly how a dead setting gets laundered into a
live-looking one — the same trap datasource.retryPolicy had to defuse against
hook/job retryPolicy (which spell the delay `backoffMs`) in objectstack-ai#4583. A pin test
asserts the message names them as DIFFERENT keys.

Retired ALIAS spellings (query, onError, errorHandling, errorMode, batch,
chunkSize, skipErrors, showall, location) route to the same prescriptions rather
than suggesting a rename onto a key that is also gone.

Connected, not removed — doc.tags:

`BookGroup.include` has always accepted `{ tag }`, and it could never match a
single doc in any stack. Not because the matcher was missing: `matchesInclude`
compares `doc.tags`, the book route already forwards `tags: d.tags`, and
`ResolverDoc` already declared `tags?: string[]` annotated "(P3d; absent
today)". The gap was one line at the AUTHORING end — DocSchema is strict and had
no `tags` key, so writing one was a parse error and every doc reached the
resolver with tags undefined. ADR-0049 says enforcement wins when the feature
exists; removing the variant would also have discarded working matcher code and
left authors a bare union error carrying no prescription.

ADR-0087: new conversion `mapping-inert-keys-removed` (scoped to the `mappings`
collection deliberately — a stack-wide strip would delete an enforced batchSize
from connector/sync/bulk-action/offline) plus an extension of
`app-dead-authoring-keys-removed` to drill the contextSelectors array; both
wired into the protocol-17 D3 chain step.

`allValue` was re-verified as its ledger note required: still live (the shell
reads it for auto-selection and query-param defaulting), but its describe() no
longer calls it "the value emitted when All is selected" — an event that cannot
occur and never could.

Incidental, from confirming the area gates while working the selector keys:
filterAppForUser walks only the top-level `navigation` tree and never reads
`item.areas`, so area-level visible/requiredPermissions are FAIL-OPEN, not
merely unread. Recorded accurately in the ledger and filed as objectstack-ai#4651 rather than
fixed here — inventing an authorization mechanism inside a retirement PR is
exactly what objectstack-ai#4583 declined to do for managed read-only.

mapping joins datasource at zero dead keys.


Claude-Session: https://claude.ai/code/session_01E5CYr5SDwe85gH2Jr5KSgu

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

dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation size/xl tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants