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

fix(#4509): three auth-gate disconnects close — email_template bridges, job and validation close their doors#4558
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_templatebridge 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. jobgate 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. validationkind 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 untouchedValidationRuleSchema 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