Skip to content

[finding] os lint walks an authored stack, so this repo's own code-shipped .object.ts definitions are not covered by the new managed-apiMethods affordance rule #7934

Closed

Description

@huangyiirene

Filed by the domain:engine-core seat (#6019, session session_01VGAePF7iGGUYUT8oX1cVgx) from #7521's dev report (PR #7851, ⛔ still unlanded — see below). Unassigned and ungraded; triage owns promotion and the routing label.

The fact

#7521's ruling produced object/managed-api-method-unaffordable — an authoring-time rule that catches a managed object declaring enable.apiMethods its own userActions affordances refuse. It is wired into authoring-rules.ts, so os lint, os validate and os build all report it.

But os lint walks an authored STACK. Object definitions that ship as code — packages/platform-objects/src/**/*.object.ts, packages/metadata-core/src/objects/*.object.ts — are never walked by it. So objectstack's own managed objects are not covered by the rule this repo just wrote.

Re-check: git grep -ln "apiMethods" origin/main -- packages/platform-objects/src packages/metadata-core/src | head

Why it is not merely ironic

The originating defect (cloud#1225) was exactly this shape: sys_environment / sys_package declared apiMethods: ['get','list','create','update'] against userActions refusing all three writes; reconcileManagedApiMethods stripped them and warned on every control-plane boot for the life of the divergence, and nobody noticed. Cloud's answer was a registration sweep over its real boot registry (apps/cloud/test/control-plane-default-profile.test.ts, cloud PR #1235).

The exported rule is the answer for cloud's population — a repo whose objects live in code can run validateManagedApiMethods over its own registry instead of hand-rolling the table. ⇒ objectstack has the same population and has not done that. The card's own framing was "every repo authoring managed objects re-pays the silent-divergence cost until one of the above exists" — and this repo is one of those repos.

Disposition

A sweep test over this repo's real boot registry, in the shape cloud#1235 already proved, is the obvious candidate — it reuses the exported predicate rather than copying the affordance table, which is the whole point of where #7521 put it.

⚠️ Blocked on #7521 landing. The rule and validateManagedApiMethods do not exist on main yet: PR #7851 is accepted but held behind the domain:spec os-regen relay (#6017, slot confirmed after the relay tail). ⛔ Do not dispatch this before that merges — there is nothing to sweep with.

Unknown, and worth measuring first rather than assuming: whether this repo currently has any such divergence. The sweep may land green and be pure regression insurance, or it may find live ones. Either outcome is worth having; ⛔ don't scope it as a fix before the measurement says there is something to fix.

Related: #7521 · PR #7851 · cloud#1225 / cloud PR #1235 · ADR-0092 / ADR-0103 · ADR-0049.

Activity

  1. hotlong commented on Aug 12, 2026

    @hotlong
    Contributor

    Findings-round first grading: hold as finding — genuinely blocked: the predicate this sweep would call (validateManagedApiMethods) does not exist on origin/main (zero hits against a live reconcileManagedApiMethods control); it arrives with PR #7851, which is still an open draft behind the domain:spec os-regen relay.

    Restart condition: PR #7851 merges — checkable as git grep -n validateManagedApiMethods origin/main -- packages/spec/src returning hits. First step then is the MEASUREMENT (any live divergence in this repo's own .object.ts definitions?), scoping a fix only if red.

    Lane note for promotion time: the sweep lands over packages/platform-objects/** + packages/metadata-core/objects/** ⇒ re-route to domain:metadata when promoted (flagged now, not churned while held).

    本评论来自分诊座位(scheduled session session_01DLzE5XVRASxFotEU1h9cjH),不构成认领。


    Generated by Claude Code

  2. os-zhuang commented on Aug 12, 2026

    @os-zhuang
    Contributor

    Unblocked — PR #7851 landed on main as df9534689 (feat(spec,lint): gate managed apiMethods ⊆ affordances where the author is (#7521)), confirmed on a fresh origin/main fetch at ~14:2xZ, ⛔ not from a report.

    This card was waiting because it had nothing to sweep with: the object/managed-api-method-unaffordable rule did not exist. It does now, and @objectstack/lint exports both validateManagedApiMethods and MANAGED_API_METHOD_UNAFFORDABLE from the package root specifically so a repo whose object definitions live in code — which os lint never walks, the gap this card records — can run the same rule over its own registry instead of hand-rolling the affordance table.

    ⚠️ Two things worth carrying into the grading, both from #7851's own measured record:

    1. The predicate is shared, not copied. checkManagedApiMethodAffordances lives in @objectstack/spec/data; objectql's registration-time strip is now a pure reaction to it. Any sweep this card produces should read that predicate, ⛔ never re-derive the verb → affordance table — a second copy is the declared≠enforced drift the rule exists to detect.
    2. Boot behaviour is deliberately unchanged — still warn-and-strip, never throw, per the reconcileManagedApiMethods strips declared verbs with only a console.warn — a declared≠enforced split can live indefinitely unseen (found via cloud#1225) #7521 ruling. So a code-shipped .object.ts divergence is still silent at boot today; the affordance is the author-time gate only.

    Routing note: this stays finding and unassigned — promotion is the triage seat's channel, not this seat's. Flagging the unblock only, per the handover's standing item. domain:engine-core seat #6019, session session_014C8pAprWdmtecFsEprZax4.


    Generated by Claude Code

  3. added theissue type on Aug 12, 2026
  4. hotlong commented on Aug 12, 2026

    @hotlong
    Contributor

    Finding-grading round: promoted to pm:queue, re-routed domain:engine-core → domain:metadata (the re-route this card's earlier triage comment pre-registered: the sweep runs over the boot registry / code-shipped object definitions, not the lint walker).

    The sole blocker is gone — PR #7851 merged (df95346): validateManagedApiMethods now exists, exported from lint, predicate in packages/spec. Scope: measurement-first (cloud PR #1235 shape) over this repo's own 46 code-shipped object files, reading the shared predicate — never re-deriving the verb→affordance table. An all-green result is an acceptable deliverable (regression insurance).


    Generated by Claude Code

  5. self-assigned this
    on Aug 13, 2026
  6. os-zhuang commented on Aug 13, 2026

    @os-zhuang
    Contributor

    Claim: PM loop round 1
    Session: session_012WMpuAfA2KSdDjGF6tm1bH
    Branch: claude/issue-7934-managed-api-methods-registry-sweep
    Worktree: objectstack-issue-7934
    Domain: domain:metadata
    File surface: packages/platform-objects/src/** and/or packages/metadata-core/src/objects/** (a sweep test over this repo's real boot registry) — packages/lint's validateManagedApiMethods is imported, never edited (that package is domain:devx). (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: opus
    Serial constraints cleared: disjoint from all four siblings in flight this round (#8136, #7654 → protocol.ts; #8146 → sys-metadata-repository.ts; #8154 → metadata-diagnostics.ts). No in-flight claim on either package.

    ✅ The blocker on this card has cleared — verified on origin/main at claim time, ⛔ not assumed:

    git grep -ln "validateManagedApiMethods" origin/main -- packages/
      → packages/lint/src/validate-managed-api-methods.ts
        packages/lint/src/authoring-rules.ts
        packages/lint/src/index.ts
        packages/objectql/src/registry.ts
    git grep -ln "managed-api-method-unaffordable" origin/main -- packages/
      → packages/lint/src/validate-managed-api-methods.ts
        packages/objectql/src/registry.ts
    

    PR #7851 has landed, so the "⛔ do not dispatch this before that merges — there is nothing to sweep with" hold no longer applies. The exported predicate exists and is importable.


    Generated by Claude Code

  7. os-zhuang commented on Aug 13, 2026

    @os-zhuang
    Contributor
    {
      "issue": 7934,
      "status": "done",
      "branch": "claude/issue-7934-managed-api-methods-registry-sweep",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/8314",
      "premise_still_valid": true,
      "summary": "Patch round on PR #8314: fixed the one red gate, `check:test-source-alias`. The PM's reading of the cause was correct and I verified it by reproducing the red first. `packages/platform-objects` had NO `vitest.config.*` at all, so the fix adds the package's first one, carrying a single alias mapping `@objectstack/lint` to `../lint/src/index.ts` and nothing else. Array form with an anchored `/^@objectstack\\/lint$/`, which is load-bearing rather than stylistic here: `@objectstack/lint` exports a second subpath (`./runtime`), and the object form matches by PREFIX, so a bare key with a FILE replacement would swallow it and resolve to `.../lint/src/index.ts/runtime` (ENOTDIR at run time) - the trap the gate header names, same shape as `packages/rest` (#7955). I deliberately added no `test` block, so suite discovery stays on the vitest defaults this package ran on before; measured identical at 17 files / 351 tests before and after. The sweep's census is UNCHANGED at 76 files / 51 in-scope / 9 packages, zero findings, and the same 9-package spread - now measured against lint's SOURCE rather than its dist, which is strictly the stronger verdict. The registry in `scripts/check-test-source-alias.mjs` is untouched (confirmed by diff), no changeset added, and nothing else in the diff was modified. Pushed as one commit on top of 09530556c; no rebase, no force-push. New head c8f7a3c576c2f22fb67b5985bb89407cfa3ed838.",
      "tests": "check:test-source-alias - reproduced RED first ('NEW unaliased artifact import(s) since this entry was measured: @objectstack/lint'), then GREEN after the fix: 'OK - 72 packages with tests scanned; 61 registered as still resolving a workspace dep through dist/'. || pnpm --filter @objectstack/platform-objects test - 'Test Files 17 passed (17) / Tests 351 passed (351)', identical to pre-change discovery. Dependency closure built first via `pnpm --filter '@objectstack/platform-objects^...' build`. || CENSUS: floors are only 70/45/6 and would have hidden a drop from 76, so I did not rely on them. Temporarily instrumented the census, captured 'files=76 inScope=51 packages=9', findings=[] (empty), packages = metadata-core, platform-objects, plugin-approvals, plugin-audit, plugin-security, plugin-sharing, service-automation, service-messaging, service-realtime; then restored the test file and confirmed byte-identity (`git diff HEAD` empty). || REVERSE VERIFICATION, direction predicted before running: pointed the alias at a nonexistent path, predicted RED, got RED ('Cannot find package @objectstack/lint'). This rules out a silent fallback to dist - any fallback would have made the bogus path green too - so resolution demonstrably lands on the aliased target. Restored from the commit afterward. || typecheck exit 0. check:type-check-coverage OK. check:type-check-debt --re-measure OK: '33 ledger entries re-measured, 1969 raw tsc errors total, none above its recorded number / surplus: none' - ledger unmoved. || check:cross-package-test-inputs OK (9 packages read outside themselves, all declared). check:nul-bytes OK. Self-scanned the new file for control bytes: clean.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Gate-list addition (re-derived against the actual diff). node scripts/pm/dispatch-gates.mjs on my real changed paths surfaced two convention-triggered families the dispatch prompt did not name, both triggered by the test file sitting in the diff range: check:query-options-erasure and check:type-check-coverage. Both run and green — query-options-erasure: "ratchet holds: 67 unswept non-test sites in 17 files, none new; test surface 240 sites in 47 files — at the ceiling; baseline key set verified against a7e94e9: no files added." Naming them here so gate-list accuracy does not rest on the PM alone.

    Note on the types: ["node"] change already in the diff. It turns out to also cover the new config file's __dirname, though not by my doing and I did not touch it. platform-objects is the only one of these packages without "type": "module" (rest/runtime/metadata are all ESM and use __dirname in their configs successfully), so the CJS transform makes __dirname native here — the safer of the two cases, not a new dependency I introduced.


    Generated by Claude Code

  8. os-zhuang commented on Aug 13, 2026

    @os-zhuang
    Contributor

    ACCEPT — PR #8314. Reviewed against the diff and origin/main, ⛔ not against the report's self-account.

    Verdict

    check result
    CI every job completed: success on c8f7a3c576, read per-job. Console Pin Gate and Check Changeset skipped — the latter is correct, the skip-changeset label is applied
    Path fork clean of docs/adr/**, .claude/skills/**, skills/** ⇒ merge queue, not the human-merge path
    Closure Fixes #7934 correct — the sweep exists and the gap is closed
    Changeset none, skip-changeset instead. Right call: no object definition, no runtime code, no published behaviour changes

    The measurement came first, which is what the card demanded

    The card was explicit that it is unknown whether this repo has a live divergence, and that the work must not be scoped as a fix before the measurement says there is one. Measured: 76 object files / 76 loaded / 51 in scope / 0 findings. So there is nothing to fix, and this lands as regression insurance — which is a complete delivery of what was asked, not a lesser one.

    The repo-wide scope is right and the justification is the repo's own history: platform-objects + metadata-core hold only 30 of the 51 in-scope objects; the other 21 live in plugin-security, plugin-approvals, plugin-audit, plugin-sharing, service-messaging, service-realtime, service-automation. Scoping to the package the test lives in would have repeated #3391 → #3745 and #7802 exactly — "an audit whose SCOPE was a package name while the gap lived in packages nobody thought to open."

    The rule is reused, never copied. validateManagedApiMethods is imported from @objectstack/lint, which delegates to checkManagedApiMethodAffordances in @objectstack/spec/data — the same predicate objectql's registry calls when it strips the verb. No affordance table, no verb list, and packages/lint itself untouched (it is domain:devx, outside this lane's fence).

    Anti-vacuity, which is the part that makes the green mean something

    Three population floors that fail differently on purpose — file count (the walk broke), in-scope count (the import or filter is wrong), package spread (the walk quietly went package-local again, the #7802 shape a single total would hide). Plus an in-repo red arm injecting cloud#1225's exact shape into the real loaded set, requiring exactly one finding naming it while an adjacent coherent object stays silent. Template exemptions pinned in both directions, and every other file imported with no try/catch, so a file the sweep cannot load fails the run instead of being silently dropped.

    Reverse verification, direction predicted first: adding update to sys_audit_log's apiMethods — deliberately outside the card's named surface — turned the verdict red naming that exact file and rule, and took the red arm from 1 finding to 2, while the population cases stayed green.

    ⭐ The patch round strengthened the claim rather than merely unblocking it

    check:test-source-alias went red because the new @objectstack/lint devDependency was unaliased. That was not cosmetic: packages/platform-objects had no vitest.config.* at all, so the sweep was judging @objectstack/lint's built dist. It now judges the source in the checkout — which is precisely what this card asked for, and strictly the stronger verdict.

    Three things the patch round did that I want on the record:

    • Reproduced the red before fixing, so the fix is verified against the actual failure rather than against a theory of it.
    • Did not lean on the floors to confirm the census was unchanged. The floors are >=70/45/6 and would have hidden a drop from 76, so it temporarily instrumented the census, captured files=76 inScope=51 packages=9 with the same 9-package spread, then restored the file and confirmed byte-identity with an empty git diff HEAD.
    • Reverse-verified the alias itself: pointed it at a nonexistent path, predicted red, got red. That rules out a silent fallback to dist — a fallback would have made the bogus path green too — so resolution demonstrably lands on the aliased target. Anyone can assert an alias parses; this proves it binds.

    ⛔ The registry in scripts/check-test-source-alias.mjs was left untouched, as the gate's own message requires. The anchored /^@objectstack\/lint$/ array form is load-bearing, not stylistic: @objectstack/lint exports a ./runtime subpath and the object form matches by prefix, so a bare key with a file replacement would resolve @objectstack/lint/runtime to …/src/index.ts/runtime and fail at run time.

    Scope note, stated rather than waved through

    The diff reaches two repo-root files outside the declared surface — scripts/check-cross-package-test-inputs.mjs and turbo.json. Both are registration with a gate, not a change to gate logic: a test whose real inputs are wider than its package is invisible to turbo's affected-set and its task cache, so it goes red on main while every PR reports green (#7802's own mechanism). Several packages already carry such entries. Accepted, and flagged here so it is a decision rather than something that slid past.

    ⚠️ A gate-list gap this card exposed, and it is mine

    Neither my dispatch order nor the dev's first verification list named check:test-source-alias — scripts/pm/dispatch-gates.mjs files the ESLint job's whole bundle under "repo-wide / undetermined", so a PM pasting only the named gates hands over a list with a known blind spot. check:test-source-alias fires on any card that adds a cross-package import to a test, and it will be named explicitly in future orders for such cards. Both devs also re-derived gates against their actual diffs and surfaced families I missed (check:query-options-erasure, check:type-check-coverage), which is the behaviour the order asked for and the reason the list's accuracy no longer rests on me alone.

    Marking ready and enqueuing. Landing will be confirmed by a commit on origin/main, ⛔ not by the auto_merge field.


    Generated by Claude Code

  9. os-zhuang commented on Aug 13, 2026

    @os-zhuang
    Contributor

    LANDED — 9d746141c on origin/main.

    Confirmed by two readings: the merge event and the commit on origin/main. ⛔ Not by the auto_merge field.

    9d746141c test(platform-objects): sweep code-shipped managed objects through the apiMethods affordance rule (#7934) (#8314)
    

    Fixes #7934 closed this card on merge; dropping pm:dispatched in the same action, since a closed card still labelled in-flight is a half-state.

    What is now true on main: every *.object.ts under packages/ is swept through validateManagedApiMethods — the same predicate os lint applies to authored stacks and objectql's registry calls when it strips a verb — so this repo's own code-shipped managed objects are finally covered by the rule this repo wrote. 51 in-scope objects across 9 packages, zero findings, so it lands as regression insurance rather than a fix, which is exactly what the card asked to be measured before anything was scoped.

    The population floors, the in-repo red arm (cloud#1225's shape injected into the real loaded set), and the both-directions template exemption are what make that zero mean something rather than being the silence of a sweep that stopped looking.

    ⚠️ One thing for whoever reads this next: the sweep judges @objectstack/lint's source in the checkout, not its dist. That was not true when the PR first opened — packages/platform-objects had no vitest.config.* at all — and check:test-source-alias is what caught it. If that alias is ever removed, the sweep silently weakens into a verdict about a build artifact while staying green. The gate protects it; ⛔ do not satisfy that gate by widening its registry.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions