Skip to content

feat(spec,lint): gate managed apiMethods ⊆ affordances where the author is (#7521) - #7851

Draft
huangyiirene wants to merge 1 commit into
mainfrom
claude/issue-7521-apimethods-affordance-lint
Draft

feat(spec,lint): gate managed apiMethods ⊆ affordances where the author is (#7521)#7851
huangyiirene wants to merge 1 commit into
mainfrom
claude/issue-7521-apimethods-affordance-lint

Conversation

@huangyiirene

Copy link
Copy Markdown
Collaborator

Fixes #7521

Executes the maintainer's 2026-08-11 08:08Z ruling — the middle option, lint/gate-visible. Boot-time behaviour is unchanged and pinned so.

The window

reconcileManagedApiMethods has always caught a managed object advertising a generic write verb its own resolved affordances refuse, and stripped it. That strip is correct and fail-closed — nothing was ever exposed. What it could not do is tell anyone: the only signal was a console.warn.

sys_environment and sys_package declared apiMethods: ['get','list','create','update'] against userActions refusing all three writes. The strip and its warning fired on every control-plane boot for the life of the divergence and nobody noticed. The split was eventually found by hand-driving the HTTP seam while writing something else, not by any gate. A boot log is not an authoring surface.

What landed

  • New shared predicate checkManagedApiMethodAffordances in @objectstack/spec/data, beside resolveCrudAffordances — the affordance authority both sides already read. The verb → affordance table (MANAGED_WRITE_VERB_AFFORDANCE) moves here from objectql's registry; it is not copied.
  • reconcileManagedApiMethods becomes a pure reaction to it. Behaviour unchanged — all 91 pre-existing registry tests pass untouched — and now explicitly pinned: it still warns and strips, and still does not throw. The warning additionally cites the new lint rule id, so an operator who greps a stripped verb out of a boot log lands on the gate.
  • New author-time rule object/managed-api-method-unaffordable (error, gating, pre-parse) wired into authoring-rules.ts, so os lint, os validate and os build all report it. Verified end-to-end through the built authoring registry against the exact sys_environment declaration.

Why the predicate is in spec, not in registry.ts

The dispatch suggested packages/objectql/src/registry.ts as the predicate's home, with a pre-authorized crossing into scripts/ or packages/lint. Measurement overturned that route, and the ⛔ non-negotiable — one predicate, never a second affordance table — is what forced the move:

  • @objectstack/lint depends on @objectstack/spec and, by its own stated package contract, never on a runtime. It cannot import objectql's table.
  • A scripts/check-*.mjs gate cannot either: every gate script in this repo is a static source scanner, and a .mjs cannot consume a TS predicate.
  • Both pre-authorized surfaces therefore structurally require a second copy of the table. @objectstack/spec/data is the only location both consumers can read.

This is exactly the checkFieldCompleteness precedent (@objectstack/spec/kernel), which serves the registry's functional-completeness warning and @objectstack/lint's validate-functional-completeness gate from one predicate under ADR-0078 — the same problem, one ADR earlier, solved the same way.

Why not a scripts/ sweep over this repo's object definitions

sys_environment / sys_package live in the cloud repo, not here — a scripts/ gate in objectstack could never have seen them. The issue's own framing is that "every repo authoring managed objects re-pays the silent-divergence cost", so the deliverable has to be shipped, reusable code. validateManagedApiMethods and MANAGED_API_METHOD_UNAFFORDABLE are exported from the package root so a repo whose object definitions live in code (which os lint never walks) can run the same rule over its own registry instead of hand-rolling the table — which is what cloud#1235 had to do.

Declared cross-package crossings

packages/spec and packages/lint, beyond the card's packages/objectql surface. packages/lint was pre-authorized; packages/spec was not — flagging it explicitly for the cross-seat declaration, with the measurement above as the reason. packages/objectql/src/engine.ts was not touched.

Gates

Green locally: both mandatory ratchets (check:query-options-erasure, check:type-check-debt — no baseline raised, no ledger entry raised), full objectql (185 files / 3278 tests), full spec (379 / 9983), full lint (71 / 1929), typechecks for all three packages, ESLint on every changed file, the full turbo build closure (70/70, including os build over the example stacks with the new gating rule active), the spec surface baselines (api-surface / export-origins / dual-source-exports / exported-any / spec-changes / generated, regenerated and re-verified against a real .d.ts build), the changeset gates, and every gate scripts/pm/dispatch-gates.mjs derived for the changed paths.

Changeset included. content/docs/releases/ untouched.


Generated by Claude Code

…thor is (#7521)

`reconcileManagedApiMethods` has always caught a managed object advertising a
write verb its own affordances refuse, and stripped it — fail-closed, nothing
ever exposed. What it could not do is tell anyone: the only signal was a
`console.warn`. `sys_environment` / `sys_package` declared
`apiMethods: ['get','list','create','update']` against `userActions` refusing
all three writes, and that warning fired on every control-plane boot for the
life of the divergence unread. The split was found by hand-driving the HTTP
seam, not by any gate.

Per the maintainer's 2026-08-11 ruling (the middle option — lint/gate-visible):

- **New shared predicate** `checkManagedApiMethodAffordances`
  (`@objectstack/spec/data`), beside `resolveCrudAffordances` — the affordance
  authority both sides already read. The verb → affordance table moves here
  from objectql's registry.
- **`reconcileManagedApiMethods` is now a pure reaction to it.** Behaviour is
  unchanged and pinned so: still warn-and-strip, still never throws — failing
  registration closed would let one metadata typo kill a control-plane boot.
  The warning now cites the lint rule id so a boot log leads to the gate.
- **New author-time rule** `object/managed-api-method-unaffordable` (`error`,
  gating, pre-parse) wired into the authoring registry, so `os lint`,
  `os validate` and `os build` all report it.

One predicate, two consumers: a second copy of this table at either end would
BE the declared≠enforced drift the rule exists to detect. Same shape, and same
reason, as `checkFieldCompleteness` under ADR-0078.

`validateManagedApiMethods` and its rule id are exported so a repo whose object
definitions live in code — which `os lint` never walks — can run the same rule
over its own registry instead of hand-rolling the table.

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

vercel Bot commented Aug 11, 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 11, 2026 8:42pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/lint, @objectstack/objectql, @objectstack/spec.

109 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/spec)
  • content/docs/automation/hook-bodies.mdx (via @objectstack/lint, 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/objectql, packages/spec)
  • content/docs/concepts/north-star.mdx (via @objectstack/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/tenancy-modes.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via packages/lint, @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/objectql, @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 @objectstack/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/examples.mdx (via packages/objectql, @objectstack/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 @objectstack/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/services-checklist.mdx (via @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 @objectstack/lint, @objectstack/spec)
  • 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/permissions/system-context.mdx (via packages/objectql, packages/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/objectql, @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/http-protocol.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.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/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/apps.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/field-grouping-and-order.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/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

7 release-owned page(s) also reference the affected code. These are read-only:

  • 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/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/lint, @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:data size/l tests tooling

Projects

None yet

2 participants