Skip to content

No gate reads repository.directory, so a package move silently 404s its own npm source link — the rule never existed #15991

Closed

Description

@os-warren

Split out of #15478 on that card's own instruction: its triage comment recommends fixing the two stale strings and adding the check in one PR, but adds the escape hatch —

⛔ If the gate turns out to want a decision (the "no declaration at all" question), land the two string fixes first and split the gate out — a p3 link fix should not wait on a policy question about the other 55.

The decision question is real and is measured below, so this is that split. #15478 lands the string corrections; this card carries the gate.

Why nothing caught it

Measured on origin/main at aa6ba0623, in a clean worktree:

  • Grepping all of scripts/ for a read of the manifest repository field returns zero hits against repository.directory. Every textual match on repository is CI's github.repository context (owner/repo) in release-github-releases.mjs and scripts/pm/check-half-states.mjs, or prose in comments.

So the answer is not "wrong population" and not "missing rule inside an existing rule set" — the rule never existed. No gate has ever read this field.

The near-miss worth naming

scripts/check-published-files.mjs has exactly the right population — every non-private workspace package — and already parses each manifest and validates several of its fields (files, types, module, exports subpaths). Its six invariants (DECLARED / COMPLETE / SUFFICIENT / MINIMAL / …) are all about the files whitelist: what the tarball contains. Nothing in it asks where the tarball says its source lives. The un-enforced notch is a category, not an off-by-one: this repo gates published package contents and does not gate published package provenance metadata.

Two adjacent gates confirm the notch rather than closing it:

The measurement a gate would start from

Census over git ls-files for every tracked package.json (81 files, node_modules excluded), comparing repository.directory against the manifest's own directory and against the filesystem. Control: a directory known to exist must report EXISTS and a deliberately impossible one must report MISSING; the census refuses to print numbers unless both fire.

before #15478 after #15478
tracked package.json 81 81
declare repository.directory 57 57
declared value resolves and equals own directory 55 57
mismatched 2 0
declare nothing 24 24

⚠️ The decision this gate needs before it can be written

The 24 manifests that declare no repository.directory at all are not a homogeneous population. Counted mechanically off the same census, not by eye: 10 are private (the docs app, five examples/*, the repo root @objectstack/spec-monorepo, a create-objectstack template, and two packages/qa/*), and 14 are publishable and simply declare nothing:

@objectstack/connector-mcp        @objectstack/connector-openapi
@objectstack/connector-rest       @objectstack/connector-slack
@objectstack/embedder-openai      @objectstack/knowledge-memory
@objectstack/knowledge-ragflow    @objectstack/plugin-approvals
@objectstack/plugin-email         @objectstack/plugin-pinyin-search
@objectstack/plugin-reports       @objectstack/plugin-sharing
@objectstack/service-sms          @objectstack/trigger-api

⭐ Note the last one: @objectstack/trigger-api sits in the same packages/triggers/ family as the two packages #15478 corrects, and declares nothing at all. The family that produced the broken links is also the family with an undeclared sibling.

⇒ Is declaring repository.directory mandatory for a publishable package, or is silence allowed?

  • Mandatory turns this into a 14-manifest backfill, and every one of those 14 npm pages currently has no source deep link rather than a broken one.
  • Silence allowed makes the gate a pure consistency check over whoever opts in — cheap and uncontroversial, but it leaves the 14 permanently unlinked and gives the next new package no signal, which is the #3786 "hand-copied line with no gate" shape this repo has already been bitten by.

That is a policy call, not a correction, which is why it is not being guessed here.

⭐ Anti-vacuity requirement, quoted from the #15478 triage

give it a --self-test, and ⭐ give that self-test an anti-vacuity anchor — a case count or a pinned roster that goes red if it runs zero cases. #15410 measured this repo at 20 of 178 self-tests that can be shown to fail on zero cases; ⛔ do not make it 20 of 179. A gate whose whole population is "manifests that declare the field" is exactly the shape that silently decays to zero when a glob changes.

This applies with full force: the population here is defined by git ls-files plus a repository.directory predicate, so both a glob change and a field rename would silently empty it. The floor should pin the measured counts above (81 / 57) the way check:dual-build-cjs-loads pins entries/packages/cjsFiles/probes.

Recurrence, for the record

The #15478 residue came from two same-day renames, not the one the card names: plugin-trigger-* to trigger-* inside packages/plugins/ (f15d6f6f6) broke the first segment, then promotion to a first-class packages/triggers/ (ea4941ad8) broke the second. Both were pure directory renames — ea4941ad8 is 16 files, 0 insertions, 0 deletions — so no content-touching diff existed for a reviewer to notice. That is precisely the defect shape a mechanical check exists for, and it will recur at the next package move.

Unassigned and untriaged.

Activity

  1. added
    toolingFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    pm:queueReady for the PM dispatch loop
    domain:servicesRetired 2026-10-22, alias of domain:server (services surface); in-flight cards keep it until landed
    on Sep 5, 2026
  2. os-warren commented on Sep 5, 2026

    @os-warren
    CollaboratorAuthor

    Correction to the "Recurrence" section — and it makes this card's case stronger, not weaker

    This card's recurrence account says the stale repository.directory survived two same-day pure renames and that "nothing rewrote the field." The Clause-② review of PR #15993 measured that and it is false in three ways:

    ⭐ The third point is the one that changes what this card is about. "Nothing rewrote the field" reads as the field was never in anyone's way — a rot nobody had the chance to catch. The measured history is the opposite: an author edited the immediately adjacent key, with the wrong line on screen, and it still shipped. And before that, a commit edited name in the same object and left directory pointing at the old path.

    ⇒ ⛔ This is not a case where a gate would have caught something no human could. It is a case where humans did look and did miss it, twice, in the same JSON block. That is a stronger argument for a mechanical check than the version this card was filed with — a rule earns its keep precisely where attention has already been shown to fail.

    ⚠️ It also sharpens what such a check must cover. A rule that only fires on directory moves would have missed 9a43e042f entirely, since that commit moved nothing — it edited a sibling field. ⇒ The trigger is "this manifest changed at all", not "this manifest moved".

    One precision nit, carried over from the same review

    PR #15993's claim that "every repository match in scripts/ is CI's github.repository context or comment prose" is imprecise — most are scripts/adr-anchors/*.json invariant strings. ⛔ The conclusion this card rests on is unaffected and was independently re-derived: 0 reads of repository.directory across 6061 code/config files, and check-published-files.mjs mentions repository 0 times, its six invariants all concerning files / exports.

    ⇒ "The repo gates published package contents and does not gate published package provenance metadata" stands as measured.

    PR #15993 is correcting the same sentence in its changeset and body; I am correcting it here so this card does not carry it into whoever takes the gate work.


    Generated by Claude Code

  3. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Sep 7, 2026
  4. self-assigned this
    on Sep 7, 2026
  5. os-warren commented on Sep 7, 2026

    @os-warren
    CollaboratorAuthor

    Claim: PM loop round R7
    Session: session_01XpTx2tbq3pZRYAdoGt6E6Y
    Branch: claude/issue-15991-repository-directory-gate
    Worktree: objectstack-issue-15991
    Domain: domain:services
    File surface: 一个新的 scripts/check-*.mjs + 根 package.json 的一条 check: 脚本 + 门族登记它需要的地方
    Container & model: M, mode:subagent
    Clause-②: no — 新增一个仓内门,⛔ 不触 packages/spec/**、不动任何发布包的产物或导出面
    Serial constraints cleared: 落 scripts/。本轮另派的两件分别落 packages/services/service-automation/src/engine.ts(#16038)与 packages/triggers/*/package.json(#15478 的拆分)⇒ 三者无路径交集。


    ⛔ 本卡只做「可选一致性检查」那一半 —— 政策问题被我划出去了

    卡自己指出了一个真实的岔路,并且明说不猜:

    ⇒ Is declaring repository.directory mandatory for a publishable package, or is silence allowed?

    ⇒ 本轮只实现「silence allowed」那一半:对已经声明了该字段的包做一致性检查。卡自己称它 "cheap and uncontroversial"。

    ⛔ 不要把 14 个未声明的 publishable 包回填。⛔ 不要让门对「未声明」报红。那是政策裁定,未决,⛔ 不在本卡内替维护者做。若你认为不裁这一刀门就写不出来,停下来报告。

    起点(卡自带的普查,⚠️ 数字要自己重测)

    卡记(#15478 之前 / 之后)
    tracked package.json 81 / 81
    声明了 repository.directory 57 / 57
    声明值可解析且等于自身目录 55 / 57
    不匹配 2 / 0
    什么都没声明 24 / 24

    ⚠️ 这些是几天前的读数,且仓一直在动。自己重测,对不上就以你的读数为准并说明差异。

    ⚠️ 一个排序风险,本轮独有 —— 先读这条再动手

    那 2 个不匹配正是 PR #15993 在修的两个 trigger 包(@objectstack/trigger-record-change / @objectstack/trigger-schedule),而 #15993 尚未落地(它一直被合并队列因 governed 面踢出,本轮另一个座位正在把那行 ADR 拆走以解开它)。

    ⇒ 如果你的门在 #15993 之前落地,它会在 main 上是红的。⛔ 不许因此去改那两个 manifest —— 那是 #15993 的围栏,动它就是撞车。你的选项:

    • 把门写好、测好,但在 PR 里明确记录这条排序依赖,让 PM 控制落地顺序(首选);
    • 或者门本身对这两个已知条目有一个带到期条件的记录(若你这么做,必须写清楚它何时该被删掉,⛔ 不许留一个永久豁免)。

    报告里要明说你选了哪一条、为什么。

    ⭐ 反空转锚点 —— 卡把它列为硬要求,我原样保留

    give it a --self-test, and ⭐ give that self-test an anti-vacuity anchor — a case count or a pinned roster that goes red if it runs zero cases. #15410 measured this repo at 20 of 178 self-tests that can be shown to fail on zero cases; ⛔ do not make it 20 of 179.

    ⇒ 本门的总体由 git ls-files 加一个 repository.directory 谓词定义,glob 一变或字段一改名,总体就会静默清空。所以:把测到的计数(81 / 57 之类)钉成字面量,像 check:dual-build-cjs-loads 钉 entries/packages/cjsFiles/probes 那样。⛔ 一个只会说「全过」的门,在总体为 0 时也会说「全过」。

    ⭐ 并且控制探针要双向:一个已知存在的目录必须报 EXISTS,一个刻意不可能的目录必须报 MISSING;两者都开火,门才允许打印数字。卡自己就是这么做的普查,照做。

    ⭐ 卡的补充评论改了它自己的成因,别漏

    评论 5548080999 更正了卡的「Recurrence」段,而且加强了立门的理由:

    • f15d6f6f6 不是重命名,是 26 文件的复制(+2222/−19),它改了 manifest 的 name 却把 directory 留在旧路径;
    • 只有 ea4941ad8 是纯重命名;
    • ⚠️ 9a43e042f(docs: update repo references for framework → objectstack rename #3380)后来改过同一个块里的 repository.url,而那行过期的 directory 就作为可见的 hunk 上下文躺在旁边。

    ⇒ 人看过,而且漏了两次。所以:触发条件是「这个 manifest 有任何改动」,不是「这个 manifest 被移动了」 —— 只在目录移动时开火的规则会完整地漏掉 9a43e042f。

    交付纪律

    ⭐ 门要先被证明能红:在一棵故意做坏的树上(或用自测夹具)让它报红,再采信它在真实树上的绿。⛔ 没见过它失败,它的绿什么也不说明。
    ⭐ 门族在最终 head 上机械推导并全跑,退出码紧接单条重定向之后捕获,⛔ 永不走管道;exit 3 PREREQUISITE NOT MET 要满足前置后重跑成真读数,⛔ 不得记作 pass。check:type-check-debt 在 4096 堆下会 OOM,本车道实测要 8192。
    ⭐ changeset:判断并说理由,⛔ 不要默认(纯仓内工具、不发布任何东西 ⇒ 很可能 skip-changeset,但要把发布面读出来再说)。
    ⛔ 不在 PR 正文预测 CI 状态。⛔ worktree-first,⛔ 永不 git stash。

    domain:services PM 席位 · 认领由 PM 在派发时写


    Generated by Claude Code

  6. os-warren commented on Sep 7, 2026

    @os-warren
    CollaboratorAuthor

    os-dev-report

    {
    "issue": 15991,
    "status": "done",
    "branch": "claude/issue-15991-manifest-repository-directory-gate",
    "pr": "#16446",
    "premise_still_valid": true,
    "summary": "Implemented ONLY the 'silence allowed' half: a new scripts/check-manifest-repository-directory.mjs (wired as check:manifest-repository-directory, a step in Lint & Repo Gates) that judges every tracked manifest which DECLARES repository.directory on three invariants — WELL-FORMED, RESOLVES (the directory is in the repository, read off git ls-files rather than existsSync, because the npm deep link is served by GitHub from the repository), and OWN. A manifest that declares nothing is counted, listed by --list and never a finding; the 14 undeclared publishable packages are untouched and the policy question stays open. ORDERING: option 1 of the two the dispatch offered — the gate carries NO exemption machinery, so it reports the 2 findings that are the fence of PR #15993, and the ordering enforces itself (this cannot land ahead of that correction without the gate saying so); option 2 (an expiring record) was rejected because a row that reds when it goes stale moves the dependency to the other side and reds the default branch the day #15993 lands, and a row that does not red is the permanent waiver the dispatch forbids. TWO DEVIATIONS TO NOTE: (a) the branch is claude/issue-15991-manifest-repository-directory-gate, not the claimed name — the first branch was pushed with a 'Refs' card-relation trailer in its commit, which check:partof-closing-keyword RULE 2 makes a blocking finding, and that gate's own header forbids repairing it by rewriting pushed history; the corrected history therefore went to a new branch, and claude/issue-15991-repository-directory-gate is dangling with no PR and can be deleted; (b) skip-changeset applied and read back (additive union over the live label set, size/xl preserved) — the diff is a repo-internal gate, a check: script in the private root manifest and a workflow step, so nothing publishes.",
    "tests": "CENSUS re-measured on this branch, matching the card's 'before' column exactly and NOT its 'after' column (#15478 has not landed): 81 tracked package.json / 57 declaring / 55 resolve-and-equal / 2 mismatched / 24 declaring nothing (10 private, 14 publishable). PROVEN RED FIRST, four ablations, each mutation confirmed on disk by counting the injected AND the removed text before any reading was taken, each restored with git checkout HEAD -- <abs path> and each restore proven by an empty git diff HEAD plus a blob hash equal to HEAD's (d2c93da524bb): (1) manifest filter returns nothing -> REFUSES exit 2, names the manifests floor and the EXISTS probe having no subject; (2) one self-test battery stops registering -> self-test exit 1, 'battery "the vacuity floors, each driven to zero" DID NOT RUN — 0 cases registered, 11 pinned'; (3) the field renamed in the predicate -> REFUSES exit 2, 'a full population, an empty subject'; (4) tracked-directory predicate stuck on true -> REFUSES exit 2, MISSING control probe FAILED. Each of those four would have printed a clean green without the instrument that caught it. A fifth attempt was a NO-OP (the perl anchor missed) and was DISCARDED and re-run with a working anchor rather than counted. PROVEN GREEN on the real population: the shipped scan driven over the live tracked listing with only the two fenced manifests patched IN MEMORY (disk untouched, git status on packages/triggers empty) -> 2 findings before, 0 after, both control probes fired, verdict census 57 ok / 24 undeclared. SELF-TEST: 72 cases, 9 batteries, roster+per-battery floors evaluated before the verdict, verdict handshake flag so an early return cannot exit 0. GATE FAMILY derived mechanically on the final head — node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands, 55 commands, identical list across three derivations — and ALL run, exit codes captured immediately after a single redirect and never through a pipe: 54 green, the 1 non-green being this PR's own new gate on the two fenced manifests. check:dts-closure, check:dual-build-cjs-loads and check:type-check-debt first answered exit 3 PREREQUISITE NOT MET; they were re-run to real readings after pnpm build through the shared verify lock (type-check-debt under --max-old-space-size=8192) and are green. check:pm-dispatch-gates: 1534 cases pass (run detached per its own header, then waited on in-turn to completion). pnpm lint (eslint . --no-inline-config, whole tree) exit 0 — the full repo-scale scan ran, so no narrowing was taken and none is claimed. Union run at 48fab7b; origin/main merged in first (clean, no regen-pending, diff vs main is exactly the 3 files).",
    "mcp_calls": "10 — 1 failed label read (get_labels does not resolve a PR), 1 pull_request_read, 1 create_pull_request, 2 list_pull_requests (label read + comparative read-back), 1 issue_write for the label, 1 targeted search_issues for dedup, 1 issue_write to file the finding, this comment, and its read-back",
    "open_questions": [
    {
    "question": "The card's own undecided question, restated because this PR is written to leave it open: is declaring repository.directory MANDATORY for a publishable package, or is silence allowed? 14 publishable packages declare nothing; each of those npm pages has NO source link rather than a broken one.",
    "options": [
    "A — silence allowed (what this PR implements, and nothing more): the gate stays a consistency check over whoever opts in; the 14 stay permanently unlinked and a new package gets no signal.",
    "B — mandatory: a 14-manifest backfill plus one added invariant in this gate (a publishable manifest that declares nothing is a finding). The gate is written so that is a small, contained change, and its header says explicitly that the change is one line and the decision is not."
    ],
    "recommendation": "B eventually, but NOT in this PR and not by a dev seat. The #3786 'hand-copied line with no gate' shape is exactly what leaves the next package unlinked, and @objectstack/trigger-api sits in the same family as the two broken links declaring nothing at all. It is a policy call about 14 published packages, so it wants a maintainer ruling and its own card; this PR deliberately makes the cheap half landable without it."
    }
    ],
    "out_of_scope_findings": [
    "filed as #16447: extractWatchHints drops any watch-hint literal that OPENS with a glob, so a gate declaring **/package.json is judged correctly by hintCovers and declares a population of ZERO to the derivation — check:watch-hint-literal asks whether the declaration is a literal, and nothing asks whether the literals are admissible. Noticed only because check:pm-dispatch-gates reddened on this gate's root walk; a gate without a root walk would have been silently unplaceable. Deduped with one targeted search_issues (20 hits reviewed, no duplicate; the nearest neighbours #12470/#12299/#12371 are the depth-stripping and single-segment classes, not admission)."
    ]
    }


    Generated by Claude Code

  7. os-warren commented on Sep 7, 2026

    @os-warren
    CollaboratorAuthor

    ACCEPT — PR #16446. Verified by PM measurement on the PR's own head 48fab7beb, not from the report.

    ⚠️ This PR is RED on purpose and must NOT land before PR #15993. Holding it is the point — see disposition at the bottom.

    Checklist

    item verdict
    Fence: 3 files, all repo-internal ✅ scripts/check-manifest-repository-directory.mjs · package.json (the check: script) · .github/workflows/lint.yml (the step). No governed path.
    ⛔ Policy question left open ✅ The 14 undeclared publishable packages are untouched, and the gate's own refusal text says so: "⛔ Not repaired by deleting the declaration: whether a publishable package MUST declare this field is an open policy question (#15991) and is a maintainer's to answer."
    Trigger is "the manifest changed", not "the manifest moved" ✅ it judges every declaring manifest on every run
    Census re-measured ✅ 81 / 57 / 55 / 2 / 24 — the card's before column, correctly, because #15478 has not landed

    The gate's reading on the real tree, run by me

    EXIT=1
    ✗ check:manifest-repository-directory — 2 finding(s) across 57 declaring manifest(s):
      ✗ packages/triggers/trigger-record-change/package.json: declares
          'packages/plugins/plugin-trigger-record-change', which is NOT a directory in this
          repository … This manifest lives at 'packages/triggers/trigger-record-change'.
      ✗ packages/triggers/trigger-schedule/package.json: … same shape
    

    Exactly the two the card named, and nothing else. Self-test: 72 cases pass.

    ⭐ The anti-vacuity floor — my own ablation, and this is the requirement the card cared most about

    The card's hard line was "give that self-test an anti-vacuity anchor … #15410 measured this repo at 20 of 178 self-tests that can be shown to fail on zero cases; ⛔ do not make it 20 of 179."

    Predicted: emptying the manifest population makes the gate refuse, not print a clean green. Observed:

    EXIT=2
    REFUSES — the EXISTS control probe has no subject — no manifest in the population sits in
      a directory, so nothing in this run is KNOWN to be tracked and the predicate went
      unexercised in that direction.
    REFUSES — measured only 0 tracked package manifest(s), below the floor of 72 (81 on 3e7ef9c238).
      This is the whole population. The manifest filter matched (almost) nothing, so every
      invariant below it was satisfied over an empty set and this gate printed what a clean
      tree prints.
      ⛔ NOT a pass: nothing, or nearly nothing, was read.
    
    HEAD blob     1bba38464b97416bb3ac4874b233d8444c1378a0
    mutated blob  3e93bae3d5ed5ab855a5c04509ef845c14d506fd
    restored      1bba38464b97416bb3ac4874b233d8444c1378a0   marker 0, `git diff HEAD` empty, tree clean
    

    Two independent refusals fire, and the floor is pinned to a census on a named commit rather than to a number someone typed. ⇒ 20 of 178 stays 20 of 178.

    The ordering call was the seat's, and it made the right one

    I offered two options. It took option 1 — no exemption machinery, the gate simply reports the 2 findings — and rejected option 2 with an argument I had not made:

    an expiring record reds the default branch the day #15993 lands, and a row that does not red is the permanent waiver the dispatch forbids.

    ⇒ Correct, and better than what I asked for: the ordering now enforces itself. This PR cannot land ahead of the correction without the gate saying so out loud.

    Deviation, declared: the branch name changed, and the reason is a gate that landed today

    The claimed branch was pushed with a Refs card-relation trailer in a commit. check:partof-closing-keyword RULE 2 makes that a blocking finding — and that gate's own header forbids repairing it by rewriting pushed history, so the corrected history went to a new branch (claude/issue-15991-manifest-repository-directory-gate); the original is dangling with no PR. ⚠️ That is PR #16371, landed this evening, catching a real case within hours of shipping.

    ⭐ Also worth recording: a fifth ablation attempt was a no-op (the anchor missed) and the seat discarded it rather than counting it. That is the same trap I fell into on #16408 today, where a missed two-occurrence anchor produced a clean run that looked exactly like "the pin does not discriminate."

    Out-of-scope, filed not fixed: #16447 — extractWatchHints drops any watch-hint literal that opens with a glob, so a gate declaring **/package.json declares a population of zero to the derivation. Found only because check:pm-dispatch-gates reddened on this gate's root walk; a gate without a root walk would have been silently unplaceable.

    ⚠️ Disposition — held, not released

    domain:services PM seat · verification by local measurement and one ablation, not from the seat's report


    Generated by Claude Code

  8. os-warren commented on Sep 7, 2026

    @os-warren
    CollaboratorAuthor

    Ordering dependency discharged — #15993 landed, the gate is green, #16446 released.

    This is the promise from the ACCEPT above, kept and measured rather than assumed.

    1. The prerequisite landed. git log origin/main | grep -c '(#15993)' = 1, cwd control (#15365) = 1. And the two manifests it corrects now read their own directories on origin/main:

    packages/triggers/trigger-record-change -> packages/triggers/trigger-record-change
    packages/triggers/trigger-schedule      -> packages/triggers/trigger-schedule
    

    2. The gate re-run on a tree that carries the fix — origin/main forward-merged into this branch through scripts/pm/os-regen-merge.sh (exit 0, nothing pending regeneration, working tree clean), new head 294a3f058, path surface still exactly the three files:

    EXIT=0
    ✓ check:manifest-repository-directory — 57 of 81 tracked manifest(s) declare
      repository.directory, and every one of them names its own directory;
      24 declare none (not judged: policy question #15991);
      2 control probe(s) fired, EXISTS on 'apps/docs' and
        MISSING on 'apps/no-such-directory-this-gate-invents'.
      provenance — manifests/declaring/trackedDirectories: this run 81/57/527 ·
        floors 72/50/460 · derived from 81/57/527 measured on 3e7ef9c238 (=/=/= vs the record).
    

    ⭐ 0 findings is only worth reading because the line beside it says both control probes fired and the population matches its pinned census exactly. A green with no subject would have been refused — that is what I ablated earlier, and it exits 2. Self-test re-run on the merged head: exit 0.

    3. Released. Flipped ready, auto-merge armed.

    ⛔ The policy question this gate deliberately leaves open — mandatory vs. silence-allowed for the 24 manifests that declare nothing, 14 of them publishable — is untouched and unprejudiced. The gate's own verdict line names it (not judged: policy question #15991), so it stays visible on every green run rather than fading. Still the maintainer's.

    domain:services PM seat · the forward-merge and both readings are mine


    Generated by Claude Code

  9. hotlong commented on Sep 7, 2026

    @hotlong
    Contributor

    Ruling recorded — A: repository.directory is mandatory for a publishable package; the gate reds on silence and the 14 undeclared manifests are backfilled (director seat, summon #17, decision batch #3, 2026-09-07)

    Provenance (who / verbatim / where): maintainer, live PM chat with the director seat (session_01XesLUWmuhjuRwmU618AZ1M), 2026-09-07T14:5xZ, batch #3 presented as 1(1) · 2(1) · 3A · 4C · 5A with this card as item 5 recommending A (the services seat's four-facet block 5568608930); reply, verbatim: 「同意」.

    Ruled. A publishable (non-private) workspace manifest declares repository.directory naming its own directory. scripts/check-manifest-repository-directory.mjs gains the one invariant its header calls "one line, the decision is not": a publishable manifest that declares nothing is a finding. The 14 manifests listed on this card are backfilled in the same PR — one line each, mechanical; private manifests stay unjudged. The self-test's pinned census moves with it (declaring 57 → 71, undeclared publishable 14 → 0), floors re-derived on the new head, the anti-vacuity anchors kept (#15410: 20 of 178 stays 20 of 178), and the verdict line stops naming an open policy question. Option B (silence allowed) is refused: it leaves "remember to write this line" to memory, the #3786 shape this repo has already been bitten by, on a defect a reviewer cannot see in a diff.

    Execution, domain:services lane: one PR — the invariant, the 14 backfills, the census pins. Clause-②: no (published metadata; no accept set moves). Changeset: 14 published packages' manifests change, so the dev reads the publish surface and judges — patch per package, or skip-changeset with the reason written; ⛔ not defaulted.

    Labels: needs-user-decision → pm:queue in one write, read back. Blocked-by: none (#15478 and #16446 both landed). Governing text: ADR-0049 enforce-or-remove; the #15478 triage's anti-vacuity requirement. Ledger: objectstack#12708, summon #17.


    Generated by Claude Code

  10. added
    pm:queueReady for the PM dispatch loop
    and removed
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    on Sep 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:servicesRetired 2026-10-22, alias of domain:server (services surface); in-flight cards keep it until landedfindingRecorded observation — held, not dispatchable until the findings triage round grades itpriority:p3toolingFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions