feat(spec,lint): gate managed apiMethods ⊆ affordances where the author is (#7521) - #7851
feat(spec,lint): gate managed apiMethods ⊆ affordances where the author is (#7521)#7851huangyiirene wants to merge 1 commit into
apiMethods ⊆ affordances where the author is (#7521)#7851Conversation
…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
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 3 package(s): 109 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 7 release-owned page(s) also reference the affected code. These are read-only:
|
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
reconcileManagedApiMethodshas 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 aconsole.warn.sys_environmentandsys_packagedeclaredapiMethods: ['get','list','create','update']againstuserActionsrefusing 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
checkManagedApiMethodAffordancesin@objectstack/spec/data, besideresolveCrudAffordances— 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.reconcileManagedApiMethodsbecomes 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.object/managed-api-method-unaffordable(error,gating, pre-parse) wired intoauthoring-rules.ts, soos lint,os validateandos buildall report it. Verified end-to-end through the built authoring registry against the exactsys_environmentdeclaration.Why the predicate is in
spec, not inregistry.tsThe dispatch suggested
packages/objectql/src/registry.tsas the predicate's home, with a pre-authorized crossing intoscripts/orpackages/lint. Measurement overturned that route, and the ⛔ non-negotiable — one predicate, never a second affordance table — is what forced the move:@objectstack/lintdepends on@objectstack/specand, by its own stated package contract, never on a runtime. It cannot import objectql's table.scripts/check-*.mjsgate cannot either: every gate script in this repo is a static source scanner, and a.mjscannot consume a TS predicate.@objectstack/spec/datais the only location both consumers can read.This is exactly the
checkFieldCompletenessprecedent (@objectstack/spec/kernel), which serves the registry's functional-completeness warning and@objectstack/lint'svalidate-functional-completenessgate 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 definitionssys_environment/sys_packagelive in the cloud repo, not here — ascripts/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.validateManagedApiMethodsandMANAGED_API_METHOD_UNAFFORDABLEare exported from the package root so a repo whose object definitions live in code (whichos lintnever 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/specandpackages/lint, beyond the card'spackages/objectqlsurface.packages/lintwas pre-authorized;packages/specwas not — flagging it explicitly for the cross-seat declaration, with the measurement above as the reason.packages/objectql/src/engine.tswas not touched.Gates
Green locally: both mandatory ratchets (
check:query-options-erasure,check:type-check-debt— no baseline raised, no ledger entry raised), fullobjectql(185 files / 3278 tests), fullspec(379 / 9983), fulllint(71 / 1929), typechecks for all three packages, ESLint on every changed file, the fullturbo buildclosure (70/70, includingos buildover 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.tsbuild), the changeset gates, and every gatescripts/pm/dispatch-gates.mjsderived for the changed paths.Changeset included.
content/docs/releases/untouched.Generated by Claude Code