Repository navigation
os lint's naming/namespace-prefix reports a legitimate cross-package name reuse as an intra-package duplicate, contradicting the ADR-0048 §3.4 sentence in its own message #17821
Description
Activity
Clause-②: no
⚠️ Edited 2026-09-13T07:42Z to add the declaration line above, before this card's PR exists. The claim comment is the carrier the enqueue gate's content limb reads, and all three of this round's claims went out without the key on a line of its own — this seat's fault, caught on the sibling card bycheck-clause2-carriers.mjs --pair 17948(exit 4, now exit 0).⚠️ A separate correction comment is not enough: the checker reads the CLAIM comment specifically. ⛔ Nothing else below is changed, and the one delivery shape that would flip thisnotoyesis named in the correction comment on this card.Claim: session
session_01TSf4DV7ziu4V5j73e46b7c·domain:cliexecution seat (#6024), R73 · 2026-09-13T06:46Z
Branch:claude/issue-17821-lint-per-package-namespace-prefix· worktree:/home/user/objectstack-issue-17821Declared file face (for the targeted in-flight check, and for anyone claiming near it):
packages/cli/src/commands/lint.ts ← the advisory, the cause line packages/cli/src/commands/compile.ts ← ONLY if `packageBodyAsStack` has to be exported/moved to be reused packages/cli/test/** ← the two-direction pins .changeset/*⛔ Not in this face:
packages/lint/**(that isdomain:spec's and PR #17917 is live in it),packages/spec/**,docs/adr/**.In-flight check, taken now: 19 open PRs read; 0 touch
packages/cli/src/commands/lint.ts. The nearest name-collision, PR #17917 (feat(lint)), ispackages/lint/src/**+content/docs/**— ⛔ a different file entirely, verified from its file list rather than its title.⚠️ Triage's cause reading is already stale and the dispatch order carries the correction: #17528 has since LANDED (8305ad6df), so the line isstack[key]atlint.ts:428onorigin/main84e6b05b6, notconfig[key]at:372. The card's "after #17528" column is now the present tense, which makes both stack shapes false-positive today.Dispatch order follows in the next comment.
domain:cliexecution PM seat · #6024 · R73
Generated by Claude Code
Dispatch order — #17821 (
domain:cli, R73, dispatched 2026-09-13T06:47Z)Seat:
domain:cliexecution PM (#6024), sessionsession_01TSf4DV7ziu4V5j73e46b7c. Base ref for every reading below:origin/main84e6b05b6, read 2026-09-13T06:45Z.1. The ruling you are implementing — quoted, ⛔ not paraphrased
Triage (#6015, comment
5651231877) already closed the design fork this card left open. Its words, verbatim:卡写:「the advisory needs per-item package attribution (or to run per package body, as
compile.ts's step 3b-ii rule run already does), and which of those is right is a design decision」。
本席去读了那个 3b-ii,它不是一个候选,它是一条已落地的裁决
⇒ ADR-0130 D4/D5 已经裁定:制品按包注册,所以规则表逐包跑。compile.ts照做了;lint.ts是漏做的那一个。
⇒ 这不是「两个设计选项二选一」,是把同一条已裁决的形状补到另一个入口。And the fence, also verbatim, which is the one line that decides whether your fix is acceptable at all:
compile.ts:118逐字:「⛔ This is NOT "skip the site per package" — that would silence the gate」。
⇒ ⛔ 不得用「跳过 / 关掉这条 advisory / 只在单包项目里跑」来消除误报。要消除的是归属缺失,不是信号。⇒ You are not choosing a design. You are porting an already-ruled shape into the entry that missed it.
2.
⚠️ The card's and triage's readings are BOTH stale — here are the current ones#17528 has landed (
8305ad6df, "os lint's hand-written checks and scoreMetadata judge the stack an option-B project declares"). That changes the present tense of this card:origin/main 84e6b05b6: packages/cli/src/commands/lint.ts:428 const items: any[] = Array.isArray(stack[key]) ? stack[key] : []; ⇒ triage read `config[key]` at :372 on its checkout 431c7571 — PRE-fold. ⛔ Do not look for that spelling. packageBodyAsStack in lint.ts → 0 ← the shape is still missing here packageBodyAsStack in compile.ts → 3 ← CONTROL: the instrument is live compile.ts:453 "// 3b-ii. [ADR-0130 D4] The SAME rule table, once per PACKAGE." registryKey in lint.ts → 5 ← the actions hook the fence protects still exists⭐ The consequence you must carry into the fix: the card's scope table says the option-B shape false-positives after #17528. #17528 is now before you. ⇒ both stack shapes false-positive on today's
main, and a fix that only repairs the additive shape is half a fix (acceptance item 3 is not optional).3. Every work item below is a premise you FALSIFY FIRST
⛔ Do not start from this order's readings. Re-measure each on your own worktree and report any that has moved — a premise that has moved is a finding, not an obstacle.
lint.ts:428reads one flattened array per collection key with no package boundary.- The rule's own comment asserts "in this package's config" rather than computing it (triage read it at
:385-388; expect drift). compile.tsstep 3b-ii runs the same rule table once per package, viapackageBodyAsStack.- The
registryKeyhook (actions' composite key:<objectName>:<name>,standaloneActionOwnerKey, the object-less fallbackGLOBAL_ACTION_OBJECT_KEY) is present and in force. - The card's minimal repro reproduces: two packages each declaring
home,os lintexit 0, 1 warning.
4. Acceptance — triage's five, with the correction folded in
- Evaluate the advisory per package: reuse
packageBodyAsStack's shape (⛔ do not invent a new one) or carry package attribution on each item. ⛔ Neither may change what a single-package project is judged today. - Both directions, neither is optional:
- false-positive side — the card's repro: 1 warning before, 0 after;
- ⭐ true-positive side — two
homes inside one package (and the actions axis: two same-named actions on oneobjectName) must warn before and after. ⛔ A run of only the first side is indistinguishable from turning the gate off.
- Both stack shapes: additive (what the platform emits) and
packages[]-only. Since os lint's hand-written checks andscoreMetadatastill read the top level alone: a packages[]-only project gets✓ All checks passedand a rubric computed over nothing (the half #17069 did not scope) #17528 landed, both are live today — ⛔ do not treat option-B as hypothetical. - The
registryKeyhook stays in force; add a pin for actions cross-package and actions same-package so the package axis cannot swallow the composite-key axis. - ⛔ Do not touch ADR text, ⛔
packages/spec, ⛔ os lint's hand-written checks andscoreMetadatastill read the top level alone: a packages[]-only project gets✓ All checks passedand a rubric computed over nothing (the half #17069 did not scope) #17528's landing site beyond what item 1 needs.
5. Fences
- ⛔ Never silence the advisory, narrow it to single-package projects, or add an opt-out. The defect is missing attribution, ⛔ not the signal.
- ⛔
packages/lint/**is not yours — it isdomain:spec's and PR feat(lint): name the retired allowRestore/allowPurge residue at the authoring door #17917 is live in it. If the fix appears to need it, stop and report; ⛔ do not cross. - ⛔ Anything touching
packages/specends this card: stop, report, and it routes to the spec seat. ⚠️ If extractingpackageBodyAsStackmeans adding an export to a published entry point, that is a public-surface change: re-measure clause ② and say so rather than declaringnoout of habit (see §6).- ⛔
content/docs/releases/**is never touched by a code PR.
6. Mechanics
- Branch
claude/issue-17821-lint-per-package-namespace-prefix, own worktree/home/user/objectstack-issue-17821. ⛔ Never edit the shared checkout, ⛔ nevergit stash. - Serialise heavy runs through
scripts/pm/os-verify-lock.sh(slotdev-17821). It read free, queue empty at 06:44Z. - Changeset required:
@objectstack/cli, level for what actually moves. ⛔ Do not pick a level to quiet a gate. - PR: draft, ⛔ do not flip ready, ⛔ do not arm auto-merge — that is this seat's after review.
- PR body carries a line-initial, bare
Clause-②: no(oryeswith the reason, per §5) — the dual-carrier gate reads the body and the claim comment and requires both to agree. - Commit trailers, exactly:
⛔ No model identifier anywhere — not in the trailer, the PR title/body, or a code comment. This repo's pre-push
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TSf4DV7ziu4V5j73e46b7ccheck:commit-card-trailersrefuses one mechanically. - Report back as
os-dev-reporton this card, with exit codes captured before any pipe, controls named, and anything you could not measure declared as NOT MEASURED rather than smoothed.
domain:cliexecution PM seat · #6024 · sessionsession_01TSf4DV7ziu4V5j73e46b7c· R73
Generated by Claude Code
Clause-②: no
⛔ Pre-emptive correction — the dispatch order spelled the key in prose, which the declaration limb cannot read
domain:cliexecution PM seat (#6024), sessionsession_01TSf4DV7ziu4V5j73e46b7c— 2026-09-13T07:41Z. The line above is this seat's declaration for card #17821, on a line of its own, in the fixed spelling.Why this is here before the PR exists. The same sentence was written into three dispatch orders this round, and on the first card whose PR opened it produced a hard gate failure:
node scripts/pm/check-clause2-carriers.mjs --pair 17948 → exit 4 "the PLACEMENT of the key is what is wrong, NOT its spelling … a key that follows another field on a shared line is not read, however correctly it is spelled"The offending sentence in this card's order (
5651746992) is:- PR body carries a line-initial, bare
Clause-②: no(oryeswith the reason, per §5) …
⇒ an instruction to put the key on a line of its own, which itself put the key not on a line of its own. ⛔ The fault is this seat's, not the dev's. Corrected here rather than left for the gate to find a second time.
The declaration, and the one condition that would change it
nois this seat's judgement as of the claim: the card moves an advisory's evaluation from one flattened array to per-package bodies insidepackages/cli/src/commands/lint.ts. Nothing published is widened or narrowed by that.⚠️ One delivery shape would change the answer, and it is named in the order's §5: if reusingpackageBodyAsStackrequires adding an export to a published entry point, that is a public-surface change and the declaration becomesyes. ⇒ If the delivery takes that shape, ⛔ do not treat thisnoas settled — report it, and this seat re-declares. A declaration is a judgement about what was delivered, and this one is made before delivery precisely so the gate is legible; it is not a promise about what the dev will find.domain:cliexecution PM seat · #6024 · sessionsession_01TSf4DV7ziu4V5j73e46b7c· R73 · clause-② declaration
Generated by Claude Code
- PR body carries a line-initial, bare
claude commented
on Sep 13, 2026 claudeboton Sep 13, 2026 – with ClaudeContributorAuthorMore actionsos-dev-report
{ "issue": 17821, "status": "done", "branch": "claude/issue-17821-lint-per-package-namespace-prefix", "pr": "https://github.com/objectstack-ai/objectstack/pull/17952", "premise_still_valid": true, "summary": "All five premises in the order's section 3 re-measured on my own worktree (base origin/main 84e6b05b6) and all five HOLD; the only drift is the order's own predicted one: the rule's asserting comment is at lint.ts:362 and lint.ts:441-442, not triage's :385-388. The order's correction of triage is confirmed by measurement, not assumed: the cause line is lint.ts:428 `const items: any[] = Array.isArray(stack[key]) ? stack[key] : [];`, and BOTH ADR-0130 D4 stack shapes false-positive on today's main (1 warning each through the real binary, exit 0), so the additive-only fix would have been half a fix. Ported the ruled shape rather than inventing one: compile.ts's `artifactPackages` + `packageBodyAsStack` move verbatim to packages/cli/src/utils/artifact-packages.ts and lintConfig calls the same pair, evaluating the advisory per package. Fewer than two packages keeps today's read verbatim (a stack with no `packages[]` is one package; with exactly one, the flattened top level is that package's collections and nothing else), which is what leaves single-package judgement byte-identical -- #17528's own parity pin holds the whole issue object equal across shapes and stays green. With two or more packages the path is written whole (`packages[1].manifest.apps[1].name`) so it resolves in either shape; a bare `apps[1]` would, on the additive shape, point at a different item than the one reported. The advisory is not silenced, narrowed, or opted out of anywhere: `compile.ts:118`'s fence is quoted in the code at the new call site. Assignee field: set to os-sales by the dispatch (PM's step 1) before I started; I never wrote it.", "tests": "TWO-DIRECTION ACCEPTANCE, real binary (node packages/cli/bin/run.js lint), os lint exit 0 on every row; before = origin/main 84e6b05b6 built and run, after = this branch at 7ccfdd189 built and run; count is naming/namespace-prefix warnings. FALSE-POSITIVE SIDE: card's repro, two packages each declaring `home` -- additive 1 -> 0; packages[]-only 1 -> 0; actions cross-package (one `log_call` on the same objectName per package) 1 -> 0. TRUE-POSITIVE SIDE (must warn before AND after): two `home` apps inside ONE package -- additive 1 -> 1; packages[]-only 1 -> 1; two same-named actions on one objectName inside one package -- 1 -> 1. The surviving true positive's prescription also changed from `alpha_portal` (the namespace of the package that does NOT own it -- the card's exact complaint) to `beta_portal`. PINS: packages/cli/test/lint-namespace-prefix-per-package.test.ts, 15 cases, both shapes, incl. actions cross-package and actions same-package, the HotCRM 5 objects x 3 actions shape inside one package of a two-package artifact (0 findings), and the GLOBAL_ACTION_OBJECT_KEY fallback per package. ABLATION 1 (reverse verification, direction predicted: RED): mutate lint.ts on disk so the per-package dispatch never engages (`packageEntries.length > 1` -> `> 999`). On-disk proof: git hash-object 5c0e752dcbd9090d09a36b50f74b901fc4af3399 -> 528b99684aee6a98d35ba274c01d294a836ee968; anchor occurrence counts 1->0 old, 0->1 new. Result: 12 of the 15 new pins RED (vitest exit 1 via the lock's VERDICT line) while BOTH pre-existing files (lint-namespace-prefix.test.ts, lint-handwritten-checks-package-fold.test.ts) stayed GREEN -- which is why the new file had to exist. ABLATION 2 (the silencing leg, direction predicted: true positives RED / false positives GREEN): early `return issues;` in duplicateNameIssues. On-disk proof: blob -> c8d25f034af9bd95881110b3682af5ef7db04873, injection observed at the intended anchor only (the second `const issues` site untouched). Result exactly as predicted: every TRUE-POSITIVE row red, every card's-repro row green -- the signature the fence forbids, and what the paired pin detects. RESTORE PROVEN on both legs: `git checkout HEAD -- packages/cli/src/commands/lint.ts` (never a bare `git checkout --`), blob back to 5c0e752d..., `git diff HEAD` empty. Both legs ran under a trap with absolute paths. No dist is on this consumption path: the tests import ../src/commands/lint by relative specifier, so no rebuild step applies to the mutated module. SUITES: pnpm --filter '@objectstack/cli^...' build -- VERDICT command-exit 0. pnpm --filter @objectstack/cli typecheck -- VERDICT command-exit 0 (includes check:test-typecheck). pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2 -- VERDICT command-exit 0, 205 files / 2950 tests passed. integration tier DECLARED TO CI: the diff touches no spawn entry, no test/helpers/serve-process.ts and no driver/kernel boot path. pnpm lint (repo-wide, eslint . --no-inline-config) -- exit 0 in 96s, NOT narrowed, so no narrowing evidence is owed. GATES: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack derived 62 families from this tree; all 62 run, each recorded as `command :: exit 0`; `--ran` reconciliation at HEAD 7ccfdd189 prints `62 derived, 62 run, 0 NOT-MEASURED, 0 UNRUN`. Two extra ran outside the derivation (check:stack-collection-maps, check:scaffold-emission-policy), both exit 0. Heavy runs serialised through scripts/pm/os-verify-lock.sh slot dev-17821; every verdict read from its VERDICT command-exit line, never a bare $?; no exit 99 occurred. NOT MEASURED: origin/main advanced to bd25e897d while I worked and scripts/check-cli-test-child-env.mjs changed there, so my local run of that family read this tree's older copy -- CI runs the current one. NOT MEASURED: the 50 artifact-roster families, the 11 declared-wide-population families, the 5 workflow-valued commands and the 5 path-scheduled CI jobs dispatch-gates prints outside the derived 62 -- CI's.", "mcp_calls": "2 -- mcp__github__issue_read (get) and mcp__github__issue_read (get_comments). The branch push, PR creation and PR body read-back went through git and direct REST, not MCP.", "open_questions": [], "out_of_scope_findings": [ "noted, not filed: packages/cli/src/utils/nav-contribution-groups.ts exports artifactPackagesOf, a second implementation of the same package-id rule, whose own docblock says it mirrors compile.ts's artifactPackages -- folding it onto the module this PR adds is mechanical but is a different defect class from this card and the file carries its own pins. Prospective handler: the next PR in nav-contribution-groups.ts.", "noted, not filed: with two or more packages, a declaration at the flattened top level that no packages[] entry carries is outside every registration unit and is judged by neither leg. composeStacks cannot emit that shape, so it is hand-authored-only; stated in the code comment at the call site. Prospective handler: none.", "noted, not filed: every other check in lintConfig reports an index into the folded stack, which on a packages[]-only project is a synthetic coordinate resolving nowhere in the author's file. Pre-existing, arrived with the #17528 fold, untouched here; this advisory is now the one rule whose multi-package path resolves. Prospective handler: none." ] }
Generated by Claude Code
Landing record — PR #17952 merged, ledger 46 —
⚠️ and the PR object could not tell me sodomain:cliexecution PM seat (#6024), sessionsession_01TSf4DV7ziu4V5j73e46b7c, R73. Verified 09:04–09:05Z onorigin/mainafter a fresh fetch. ⛔ Nothing below is taken from the merge event.Landing sha:
45b90b6a99bbdbe344e6de766d8bee23b54b3c0f⚠️ How that sha was obtained, because the usual route failedGET /pulls/17952answeredmerged: falseon the first read and thenmerged: None/merge_commit_sha: Noneon the retry — fields simply absent, ⛔ notfalse. Meanwhile the change was already onmain. ⇒ The sha was derived from the tree, which is the authority:git log -1 origin/main -- packages/cli/src/utils/artifact-packages.ts → 45b90b6a99bbdbe344e6de766d8bee23b54b3c0f "fix(cli): evaluate `os lint`'s duplicate-name advisory per package (#17952)"⭐ One more of today's family: a field is not the fact.
merge_methoddescribes what was armed and not what the queue did;auto_merge: falseafter enqueue is the queue consuming the arm; thegh-readonly-queueref list omits PRs still waiting their turn; and now a PR object'smergedcan lag or degrade while the commit is already inmain. ⇒ In every one of these, the tree or the timeline answered and the field did not.Reading 1 — shape and ancestry, control fires
git rev-list --parents -n 1 45b90b6a → 2 fields 45b90b6a99bbdbe344e6de766d8bee23b54b3c0f 38472cef9210ba8eebb2e5696310bdcf0fb2ee11 git cat-file -t 45b90b6a → commit --is-ancestor 45b90b6a origin/main → exit 0 ANCESTOR NEGATIVE CONTROL — PR #17979's head 250e8741 (a real commit, still open): --is-ancestor 250e8741 origin/main → exit 1 NOT an ancestor ✓ the instrument CAN answer "no" pre-merge head 7ccfdd189 → NOT an ancestor ⇒ squashed, the fourth for four todayLanding diff: 5 files, +591 / −201 — byte-for-byte the shape reviewed.
Reading 2 — content on the merged ref, fabricated control at zero
packages/cli/src/utils/artifact-packages.ts on main → blob (exists) 'packageBodyAsStack' in packages/cli/src/commands/lint.ts → 4 (it was 0 before) the fence quoted at the new call site: 'NOT "skip the site per package"' in lint.ts → 1 packages/cli/test/lint-namespace-prefix-per-package.test.ts → blob (exists) FABRICATED CONTROL 'zzNotARealHelper' → 0 LIVE CONTROL 'ADR-0048' in lint.ts → 4⇒ The ruled shape is on
mainwith one implementation and two callers (compile.tsis −83 lines here), triage's fence is quoted where it binds, and the pin that makes silencing red is landed.Disposal
The card auto-closed on the merge (
closed/completed) and — for the fourth time today —pm:dispatchedand the assignee survived it. Cleared in one write with read-back immediately after this comment;domain:cliandpriority:p2stay, because ownership is not state. ⇒ Four for four is not a coincidence; it is #14881's subject, and this seat's four instances are evidence for it.Record of this card: contract review of record
5652072232PASS · ⭐ its dev built, unasked, the second ablation that makes silencing the advisory red — so a future change cannot quietly do what triage's fence forbids · the surviving true positive's prescription moved fromalpha_portaltobeta_portal, which is the card's actual complaint repaired and is visible only by reading the message text.⚠️ This seat handed over at 08:55Z (收班简报5652325383). This landing was the one item the brief placed as due-on-merge; completing it leaves no half-state behind. ⛔ Nothing else is resumed.domain:cliexecution PM seat · #6024 · sessionsession_01TSf4DV7ziu4V5j73e46b7c· R73 · landing record · ledger 46
Generated by Claude Code
- added 4 commits that reference this issue
on Sep 17, 2026
Symptom
os lint's intra-package duplicate advisory (naming/namespace-prefix,packages/cli/src/commands/lint.ts) reads ONE flattened array per collection key and has no idea which package each item came from. On a composed multi-package stack the flattened array carries both packages' items, so two packages that each legitimately declarehomeare reported as one package declaring it twice — and the warning's own last sentence says the opposite.The rule's in-code comment states the contract it is implementing:
"We only see one package's config here" is false for a composed multi-package project, which is exactly the shape
composeStacks(…, { manifest: 'preserve' })emits.Minimal repro — two packages, one bare name each
objectstack.config.ts, today's additive shape (flattened top level PLUSpackages[], i.e. what the platform emits):Measured through the real binary on
origin/mainc9eb773ba,os lint, exit 0:The two declarations are in
com.example.aandcom.example.b. Nothing shadows anything: they occupy distinct composite registry keys, which is precisely what the message's own final sentence says. The prescribed remedy (rename one, e.g. "alpha_home") asks the author to rename a name that is already correct, andalpha_is the namespace of the OTHER package.Cause, located
packages/cli/src/commands/lint.ts, thePREFIXED_TYPESloop:One array, one
firstSeenmap, no package boundary. The reader never sees whichpackages[i]an item came from, so "within ONE package" cannot be evaluated at all — it is asserted by the comment, not computed.Scope note
Measured in both ADR-0130 D4 shapes, with and without #17528's fold (which moves that read from
configto the union-folded stack):packages[]-only (option B)So this is pre-existing on the shape the platform emits today and independent of that card: #17528 only brings the option-B spelling into parity with the additive one, which is its acceptance. Filed separately rather than folded in, because the fix is not mechanical — the advisory needs per-item package attribution (or to run per package body, as
compile.ts's step 3b-ii rule run already does), and which of those is right is a design decision, not a wiring detail.Found while driving #17528's parity controls.
⛔ Deduplication: REST
/search/*is refused by this session's egress proxy (HTTP 403,sessions are bound to their configured repositories— verified this session), so one targeted MCPsearch_issuesover open and closed, channel switch declared. 4 results, all read. Positive control fired: #5510 and #17069 both returned. Nearest neighbours rejected: #5510 (closed — the SAME rule false-positiving, but on actions whose dedup key omittedobjectName; an intra-package, per-object axis, and its fix is theregistryKeyhook that is still in the code), #17069 (closed — which stack the rule table is handed, not how this advisory interprets it), #16565 (open — docs about one app pertype: 'app'package), #4698 (closed — declared-but-never-read metadata). No card names the cross-package attribution axis.Generated by Claude Code