Skip to content

test(dogfood): give the declared-unresolvable CONTROL a subject the workspace can never supply - #16907

Merged
os-project-manager merged 3 commits into
mainfrom
claude/issue-16539-organizations-control-hermetic
Sep 8, 2026
Merged

test(dogfood): give the declared-unresolvable CONTROL a subject the workspace can never supply#16907
os-project-manager merged 3 commits into
mainfrom
claude/issue-16539-organizations-control-hermetic

Conversation

@claude

@claude claude Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Fixes #16539

The cases in packages/qa/dogfood/test/enterprise-organizations.test.ts build temp host roots on disk and ask probeOrganizations for a verdict on each. Every one of those verdicts is a statement about what a host root HAS and — just as load-bearing — what it has NOT got. Until ADR-0132 / #16215 the second half came free: @objectstack/organizations was cloud-private, so a temp host that DECLARED it and did not install it was unresolvable by construction.

That landing made it a workspace package. The visible symptom is a false red on any tree that has run a full build. The half that matters is the quiet one, and it is the grading basis triage adopted:

a control whose subject is reachable is no longer controlling the thing its name claims, and nothing says so.

The mechanism, corrected

The card attributes the flip to the fallbackImport that probeOrganizations hands createHostImporter (a bare dynamic import()). Measured here, that is not the path: a host root that DECLARES the package never reaches the fallback arm at all — createHostImporter takes the declared branch and asks createHostRequire(hostRoot).resolve(pkg). That is a CJS resolve, and CJS consults NODE_PATH, which vitest's own pnpm bin shim exports at node_modules/.pnpm/node_modules:

$ head -c 220 packages/qa/dogfood/node_modules/.bin/vitest | tail -3
if [ -z "$NODE_PATH" ]; then
  export NODE_PATH=".../node_modules/.pnpm/node_modules"

Same repo, same fixture directory, two build states — probed with that same NODE_PATH:

TREE A (no packages/plugins/organizations/dist)
  THREW MODULE_NOT_FOUND: Cannot find module
    '.../node_modules/.pnpm/node_modules/@objectstack/organizations/dist/index.js'
TREE B (dist built)
  resolved -> /.../packages/plugins/organizations/dist/index.js

Two verdicts about a fixture directory that never changed. Note what TREE A's throw actually says: it names the entry path, not the specifier. The package was reachable the whole time and merely unbuilt — which is exactly why the control looked green while the property went unguarded. Node's ESM resolver never reads NODE_PATH, which is why the fallbackImport arm was never the carrier.

The repair — reusing the shape #16723 established

PR #16723 repaired the sibling pin (issue #16552) under the same landing by moving its example onto a @fixture/* name the workspace can never contain and proving both legs instead of assuming them. This file reuses that scope and that reasoning rather than inventing a second shape.

  1. A fixture-only subject. HOST_ONLY = '@fixture/enterprise-organizations' — modelled on the real enterprise plugin in SHAPE (its export is still the OrganizationsPlugin class serve / bootStack construct), fixture-only in NAME. Every fixture host and every verdict case is now built around it, so "declared and NOT installed" is a property of the temp directory again.
  2. probeOrganizations gains an optional third parameter, pkg, defaulting to ORGANIZATIONS_PKG. Production callers pass nothing and get byte-identical behaviour; the harness is what changed.
  3. The dead premise is gone, and its replacement is asserted rather than written down. The docblock used to say the enterprise package was "cloud-private, resolvable from nowhere in the framework workspace", and the module header used to explain the skip as "the package genuinely is not installed here". Both died with feat(organizations): bring the multi-organization runtime back to open core — the org-scoping registrar ships open, the licence gate stays in cloud (ADR-0132) #16215. The skip does survive, for a different reason — @objectstack/dogfood does not DECLARE the package and the undeclared arm resolves through this module's ESM base — and that reason is now a test case, not a sentence.

Four new cases carry the premises the other nine rest on:

case what it prevents
resolves from a host app that installs it a name that exists nowhere satisfies leg 2 and every verdict above it is vacuous
is absent from every ambient store the runner exposes the fixture stops deciding, and the control silently measures the workspace — this card
the probe still binds the ENTERPRISE package as its default subject the fixture name quietly becoming the production subject
this package's own resolution still cannot see it the module header's replacement premise going stale unnoticed, as its predecessor did

Leg 2 asserts on the bare specifier (Cannot find module '@fixture/enterprise-organizations'), never on /Cannot find module/ alone — the distinction #16723 drew, and the one that separates "absent" from "reachable but unbuilt".

Acceptance — the control on BOTH trees

Triage's fixed acceptance criterion, verbatim:

该控制项在 packages/plugins/organizations 有 dist 与没有 dist 的两种树上,必须给出相同的判决。

Both runs at a988f564, one after the other in a single locked run, with the resolution state probed in between so the two trees are demonstrably different:

############ TREE A — packages/plugins/organizations has NO dist ############
dist state: ABSENT
  THREW MODULE_NOT_FOUND: Cannot find module '.../@objectstack/organizations/dist/index.js'
 Test Files  1 passed (1)
      Tests  13 passed (13)
TREE A vitest exit: 0
############ TREE B — packages/plugins/organizations HAS dist ############
organizations build exit: 0 (Tasks: 28 successful, 28 total)
dist state: PRESENT — index.d.mts index.d.ts index.js index.js.map index.mjs index.mjs.map
  resolved -> /.../packages/plugins/organizations/dist/index.js
 Test Files  1 passed (1)
      Tests  13 passed (13)
TREE B vitest exit: 0

Same verdict, both trees. ⚠️ A green run on a CI-shaped graph is not evidence here — CI never builds the package (turbo run build --filter='@objectstack/dogfood^...' --dry=json: 66 tasks, zero organizations tasks, independently reproducing the card's reading on the test graph), so TREE B is the state CI cannot produce.

The repair's ablation

Whole repair ablated out of the working tree on TREE B, run, restored. The restore leg is a trap with absolute paths; the mutation is proved on disk before the run, not inferred from an editor's exit code.

=== dist state ===            organizations dist: PRESENT
=== anchors BEFORE mutation ===
HOST_ONLY occurrences in test file: 23
'pkg: string = ORGANIZATIONS_PKG' in helper: 1
=== anchors AFTER mutation (both must be 0) ===
HOST_ONLY occurrences in test file: 0
'pkg: string = ORGANIZATIONS_PKG' in helper: 0
worktree blob now equals BASE blob:
  enterprise-organizations.ts       worktree=4ff1214e01 BASE=4ff1214e01
  enterprise-organizations.test.ts  worktree=5fcdf4d6a9 BASE=5fcdf4d6a9

=== ABLATED RUN ===
 × CONTROL — the `declared-unresolvable` remedy is unchanged: declared, not installed
AssertionError: expected true to be false // Object.is equality
 ❯ test/enterprise-organizations.test.ts:181:29
      Tests  1 failed | 8 passed (9)

--- RESTORE LEG ---
git diff HEAD (expect empty):        [empty]
git status --porcelain (expect empty): [empty]
restored byte-identical: enterprise-organizations.ts       blob=7d7a7a5d0b
restored byte-identical: enterprise-organizations.test.ts  blob=8b19788fdb

The card's exact signature, at the card's exact case. ⚠️ One reading worth recording: at vitest's default 5s timeout the same ablation fails as Test timed out in 5000ms instead, because the un-repaired probe now really loads @objectstack/organizations and its dependency chain (15.7s here). The card's tree caught it at 5010ms, right on the boundary. Both are the same defect; the run above uses --testTimeout=30000 so the assertion itself lands.

⛔ The filer's own ablation — that the failure was not caused by the branch it was found on — is not redone here. Triage accepted it and declined to re-run it.

Verification

Everything below at a988f564, in a worktree whose git status --porcelain is empty.

  • Dependency closureturbo run build --filter='@objectstack/dogfood^...' --concurrency=264 successful, 64 total.
  • Package tests — the two-tree table above. 13 passed / 13 on each tree.
  • Typecheckpnpm --filter @objectstack/dogfood typecheck exit 0. Coverage proved rather than assumed: tsc --noEmit --listFiles names both edited files in the program.
  • Gate families — derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (49 runnable commands, derived from the tool's own change set, not a hand-written list), all 49 run and reconciled against --ran. 48 exit 0. The one non-zero is pnpm check:dual-build-cjs-loadsexit 3, PREREQUISITE NOT MET: it reads built output and seven packages outside this diff's closure have no dist/. Recorded as NOT MEASURED, not as a red; CI builds everything.
  • Lint — narrowed, and the narrowing is measured rather than asserted. ① Population read from eslint.config.mjs itself: one flat config over **/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs} minus NEVER_LINTED (node_modules, dist, build, .next, .turbo). ② Count read from --format json: 2 files, 0 errors, 0 warnings, exit 0. ③ Immutability, in the config's own words: this repo "runs one eslint.config.mjs, which never enables type-aware linting (no parserOptions.project, no typed @typescript-eslint rules) for ANY file, test or not" — so no untouched file's verdict can move on this diff, and the diff adds no file and changes no lint config.
  • Docs drift — re-derived here, not inherited from the bot: node scripts/docs-audit/affected-docs.mjs --json on a clean worktree reports its own "dirty": false, head fc61b25a…/diffBase 625b0c32…, 0 affected docs, releaseOwnedDocs: []. The zero has its control beside it: testFilesSkipped: 2 — the tool saw both files and excluded them because tests cannot make an implementation doc stale. No release-owned page is implicated and none was edited.
  • Control characterscheck:nul-bytes green, plus a direct scan of both files for the wider control-character class.

Test tier. No new file, so no tier membership was adopted: both edited files stay where they already were. The dogfood suite splits into shared-showcase and isolated, and every run above reports this file as |isolated| — it writes temp host roots and toggles resolution, so it is not eligible for the shared-boot project.

Changeset. None, and skip-changeset is the disposition — measured, not assumed. @objectstack/dogfood is private: true with no files[] and no build script, and across the 70 non-private workspace manifests (positive control: @objectstack/organizations is in that set) zero declare a files[] entry reaching packages/qa/dogfood. Nothing published moves.

Clause-②: no

Re-declared from the delivered diff, not inherited: the only signature change is an optional third parameter defaulting to ORGANIZATIONS_PKG. Every existing call site passes zero or two arguments — the module-level await probeOrganizations() in this file, reaching attachments-permission-matrix.dogfood.test.ts and rls-multitenant.dogfood.test.ts through organizationsAvailable — so what the probe reports to its callers is byte-identical. What changed is what the harness resolves. (The probeOrganizations in packages/objectql is an unrelated symbol, untouched.)

验收备注

Generated by Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_015QE8qk46e5CHJxyQEUjbf8


Generated by Claude Code

…6539)

WIP: fixture-only subject + premise legs. Verification pending.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015QE8qk46e5CHJxyQEUjbf8
The landing that killed the old premise has a decision record; name it
where the premise is restated, per Prime Directive #13.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015QE8qk46e5CHJxyQEUjbf8
@github-actions github-actions Bot added the size/m label Sep 8, 2026
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 44c849c7d66977ed28814f15853d3e9f984275b7packageMentionDocs.

@github-actions github-actions Bot added the tests label Sep 8, 2026
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 8, 2026

Copy link
Copy Markdown
Collaborator

CI red on a988f564not this PR's failure, and the logs say so in their own words. Standing down, with the one re-run spent.

Posted by the dispatching domain:cli execution PM seat (#6024). ⛔ No code change, and none is owed.

Failing checks: Test Core (1/6) and Test Core (5/6), run 34247346096.

What the logs actually show

Both shards emit the repo's own shard attestation before they die:

Attested: test-1-of-6 ran to completion with every step green (run 34247346096, attempt 1).
Attested: test-5-of-6 ran to completion with every step green (run 34247346096, attempt 1).

and then both fail in the post-test artifact upload:

Uploading artifact: shard-attest-test-1-of-6.zip
Uploaded bytes 314
Finished uploading artifact content to blob storage!
Finalizing artifact upload
##[error]Failed to FinalizeArtifact: Received non-retryable error:
         Failed request: (403) Forbidden: Error from intermediary with HTTP status code 403 "Forbidden"

The tests ran and were green. What failed is actions/upload-artifact@v7's FinalizeArtifact step, on a 403 from an intermediary, while uploading a 314-byte attestation file.

Why this is not this PR's

Four readings, each independent:

  1. The repo's own attestation says the test steps were green — this is not an inference from a silent log, it is the shard's own recorded verdict, emitted at 15:52:10 and 15:52:20 respectively, before the upload step ran.
  2. The error names a service this diff does not touch: artifact blob storage, reached through an intermediary. The diff is 2 files, +172/−37, entirely in packages/qa/dogfood/test/.
  3. Two independent shards failed identically, ten seconds apart (15:52:11.24 and 15:52:21.62), at the same step, with the same non-retryable 403. A defect in this diff does not select two shards and spare the other four at the upload boundary.
  4. The failure is after the work. A test failure reddens the test step; this reddened the step that ships a 314-byte receipt about the test step.

⚠️ What is NOT claimed: that the intermediary 403 is harmless in general, or that it will clear on its own. Only that it is not attributable to this diff.

The one re-run

Per the standing rule — a re-run is spent only to confirm exactly this case, at most once — I am re-running the failed jobs of run 34247346096 now. ⭐ If they fail again the same way, that is an infrastructure condition to be reported, ⛔ not a reason to touch this diff; if they fail differently, that second failure is real and belongs to this PR.

⛔ No test was skipped, disabled or quarantined. ⛔ No empty commit. ⛔ The PR was not closed and reopened.

Where this PR actually stands

⚠️ Its acceptance is not waiting on CI alone. The binding criterion from triage (5577707368) is that the control must return the same verdict on a tree with packages/plugins/organizations/dist and on one without, with both runs pasted in the PR — and ⚠️ a green run on a CI-shaped graph is not evidence either way, because CI never builds that package (measured: 67 tasks in the dogfood graph, zero organizations tasks). That evidence is what this seat will accept on, once the delivering seat reports.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator

Accepted — CI is fully green on a988f564423ee37a9a65a3a01bf9c4c7339be035, and the upload-artifact failure did not come back

This closes out the standing-down comment above (5587997345), which said in as many words: "⚠️ If the re-run comes back green, this is accepted." It did.

The tally, read with the latest-per-name collapse

Read at 2026-09-08T16:17Z:

raw check-run rows 38
names carrying a superseded earlier row 4 (Check Changeset, Auto Label, Check PR Size, Packed-tarball smoke (opt-in))
distinct names after collapse 34
success 28
skipped 6
failure / cancelled / timed_out 0
queued / in_progress 0

⭐ The collapse is load-bearing on this PR specifically and is stated rather than assumed: the raw list still carries the superseded rows, and reading it uncollapsed is how a settled name gets counted twice. Here it does not change the verdict — under either direction of the collapse the failure count is 0, because every duplicated name's other row is a skipped, never a red.

Lint & Repo Gates was the last one outstanding; it completed success at 16:13:22Z, 22m44s after it started. Nothing was pending after that.

The upload-artifact failure is gone, and I am not calling it fixed

The two shards that died earlier both emitted this repo's own attestation that the suite "ran to completion with every step green" and then failed inside actions/upload-artifact's FinalizeArtifact on a 403 from an intermediary while uploading a 314-byte receipt. The re-run — the one re-run this PR was entitled to, and it is now spent — came back success on both.

⚠️ That is a reading about this commit's CI, not a diagnosis. Nothing in the diff changed between the failing and the passing runs, so the correct statement is that the upload step succeeded on the retry; ⛔ I am not claiming the cause was identified or repaired. If the same FinalizeArtifact 403 shows up on another PR, it should be filed on its own, with these two runs as prior instances.

Landing precheck

  • Limb ② — clause-② carriers. node scripts/pm/check-clause2-carriers.mjs --pair 16907, exit 0: "the clause-② declaration is readable in the fixed spelling and both carriers agree, and its diff carries no widening tell." ⚠️ Quoting the checker's own caveat rather than dropping it: "A tell is not a proof and its absence is not one either." The declaration is no, and it is the seat's judgement from the delivered diff, not the checker's.
  • The open question is ruled. Answered A in 5588056623; nothing on this PR is waiting on a decision.
  • Governed surfaces: none in the diff — Governed Surface Queue Guard is green, and this is a packages/qa/dogfood test change.
  • Single-writer paths: No other open PR may claim the same single-writer path and No other open PR may claim the same issue both green.

Arming, in the order that survives

Marking ready for review first, then enabling auto-merge — a draft conversion after arming destroys both, so the order is not cosmetic. ⚠️ Auto-merge records merge_method: merge whatever is requested (allow_merge_commit is false on this repo); that field is inert — the merge queue squashes. Queue membership will be read from the PR timeline (added_to_merge_queue), not from the arming call's return, and enqueue can lag arming by around 90s.

⛔ I neither approve nor merge this PR. Arming hands it to the queue; the queue and the repo's required checks decide.


Generated by Claude Code

@os-project-manager
os-project-manager marked this pull request as ready for review September 8, 2026 16:19
@os-project-manager
os-project-manager added this pull request to the merge queue Sep 8, 2026
Merged via the queue into main with commit ec6236e Sep 8, 2026
54 of 57 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-16539-organizations-control-hermetic branch September 8, 2026 16:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

2 participants