Skip to content

feat(rest): mount the compound-name per-item promotion door POST /meta/:type/:section/:name/publish (#11932) - #12105

Closed
claude[bot] wants to merge 3 commits into
mainfrom
claude/issue-11932-compound-publish-door
Closed

feat(rest): mount the compound-name per-item promotion door POST /meta/:type/:section/:name/publish (#11932)#12105
claude[bot] wants to merge 3 commits into
mainfrom
claude/issue-11932-compound-publish-door

Conversation

@claude

@claudeclaudeBot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Fixes#11932

Mounts POST /api/v1/meta/:type/:section/:name/publish — the compound-name
arity of the per-item promotion door. Both arities now come out of one
two-entry registration loop, the same shape the ADR-0033 /published read twin
has carried since #7526.

Re-derived offsets — nothing reconciled to the order

Triage measured the promote door at rest-server.ts:5936 and the published
read at :6389. PR #11985 landed in this file afterwards, so both had moved.
Measured on origin/main at 1e79aa4f8, before any edit:

$ git rev-parse HEAD
1e79aa4f812708d5d097ca33d9b7040e02b958c6
$ grep -n "publish\`" packages/rest/src/rest-server.ts
3985: * `history`, `audit`, `diff`, `publish`, `rollback`, `published`, and the
6062: path: `${metaPath}/:type/:name/publish`, <- the promote door (was :5936)
6571: // :name/publish` now carries the caller's organization
$ grep -n "published\`\|publishedPath" packages/rest/src/rest-server.ts
6413: // Registered BEFORE the compound `/:type/:section/:name/published` <- the ordering trap (was :6291)
6509: for (const publishedPath of [
6510: `${metaPath}/:type/:name/published`,
6511: `${metaPath}/:type/:section/:name/published`, <- the two-arity read (was :6389)
6515: path: publishedPath,

Every one of the three had moved. After this PR (fef09c1e6): the two-entry
loop opens at rest-server.ts:6086, path: publishPath at :6092, the
compound-name assembly at :6262, and the new ledger row at
rest-route-ledger.ts:183.

The premise held — and it is the ROUTE that was missing, not the capability

Verified rather than relayed, driving the real
ObjectStackProtocolImplementation over a sys_metadata-backed engine against
a seeded compound draft, before this route existed:

STORE before: r_draft name=crm/task state=draft
PROMOTE compound -> {"success":true,"version":"sha256:382ea457ea0096…","seq":1,
"message":"Published draft — type=object, name=crm/task [seq=1]"}

publishMetaItem keys the draft on type/name/organization/package and reads the
name's spelling nowhere. The same measurement read the whole /meta mount table
off the real registered server and confirmed the asymmetry the card describes:
PUT and GET …/published mounted in both arities, POST …/publish in one.

An unplanned corroboration: client.meta.publishItemalready constructs this
URL
— it interpolates the name unencoded and its own doc comment says
"Compound names pass through unencoded, like getItem". The SDK promised a door
the server had not mounted, and #3642's URL guard could not see it because the
compound name goes into the same ${name} slot the single-arity pattern
declares.

Route ordering — measured, and the honest shape of the constraint

The card names the first-match-wins trap. It is real for this pattern and it is
currently latent, and this PR states the difference rather than blurring it.

Measured against a real HonoHttpServer (resolveMountedRoute, the same
live-router observation the #7526 dogfood parity gate reads):

WRONG order: POST /meta/:type/:section/:name/:verb registered FIRST
resolveMountedRoute('POST', '/api/v1/meta/object/crm/task/publish')
-> { pattern: '/api/v1/meta/:type/:section/:name/:verb' }
live request -> {"answered":"sibling"} <- 200, wrong handler, no error
RIGHT order: the promotion door registered FIRST
live request -> {"answered":"publish"}
DIFFERENT arity: POST /meta/:type/:section/:name registered FIRST
-> { pattern: '/api/v1/meta/:type/:section/:name/publish' } <- cannot absorb it

So only a same-arity sibling can shadow this path. The live table has none:
the complete set of POST routes under /meta is _migrate-stored,
:type/:name/publish, :type/:section/:name/publish, :type/:name/rollback
no POST catch-all exists, unlike the GET side where
GET /:type/:section/:name really does swallow its literal siblings.

That census is pinned as a list in meta-route-registration-order.test.ts,
so the day a POST catch-all is added the latent constraint becomes live and
this reddens. Reachability itself is probed against the live router rather than
inferred from a position in the table, and the wrong-order case above is in the
suite as the demonstration.

Anti-vacuity

Ablation — predicted vs measured

Revert onlypackages/rest/src/rest-server.ts to origin/main, keep every
pin, run. The mutation is confirmed on disk with anchored greps in both
directions before each leg, and the restore is confirmed byte-identical
(git diff HEAD = 0 bytes) — an editing tool's exit code is not evidence:

LEG A: RESTORED
'for (const publishPath of' : 1 [expect 1]
'path: `${metaPath}/:type/:name/publish`,' : 0 [expect 0]
-> Test Files 4 passed (4) · Tests 87 passed (87)
MUTATE (git checkout origin/main -- rest-server.ts)
'for (const publishPath of' : 0 [expect 0]
'path: publishPath,' : 0 [expect 0]
'const publishName' : 0 [expect 0]
'path: `${metaPath}/:type/:name/publish`,' : 1 [expect 1]
git diff HEAD bytes for that file: 28793
LEG B: ABLATED
-> Test Files 4 failed (4) · Tests 26 failed | 61 passed (87)
RESTORE
'for (const publishPath of' : 1 [expect 1]
git diff HEAD bytes for that file: 0

The 26 reds, per file:

filered / total
meta-compound-publish-door.test.ts14 / 15
meta-write-door-capability-enumeration.test.ts7 / 45
meta-route-registration-order.test.ts4 / 13
rest-write-response-internal-fields.tripwire.test.ts1 / 14

No dist/ on the measured path for the ablated change. The suites import
RestServer from ./rest-server.js — a relative source import vitest compiles
from source — and packages/rest/vitest.config.ts aliases
@objectstack/plugin-hono-server to the sibling's src/. The ablation going
red is itself the proof: a dist-shadowed path would have stayed green. What
does resolve through exports to dist/ is @objectstack/metadata-protocol
(registered in check-test-source-alias.mjs's KNOWN_UNALIASED_TEST_IMPORTS
for this package) — nothing in this diff touches that package, so no rebuild
sits between the mutation and the reading; the dependency closure was built
once up front (pnpm --filter '@objectstack/rest^...' build).

Two predictions were wrong, and the corrections are the useful part.

  1. rest-write-response-internal-fields.tripwire.test.ts was predicted green
    in both states
    — I read its disposition check as one-directional (a route
    with no entry fails). It is bidirectional: it also fails on a stale
    entry naming a route that no longer exists, so ablated my new key is stale
    and it goes red. Measured, not reasoned: predicted green-both-ways, observed
    red-when-ablated.
  2. The first ablation round found a VACUOUS pin of mine.
    expect(stack.compoundPublish).toBeTypeOf('function') is true whether or not
    the route exists and passed the ablation. It is now
    expect(stack.mountedPublishDoors()).toEqual([SINGLE_PUBLISH, COMPOUND_PUBLISH]),
    read off the composed server's own table, and reddens when ablated
    (efd007b73). Round 1 measured 25 reds; round 2, after the fix, measures 26.

Green in both states — regression guards, ⛔ not red-before evidence:

  • meta-compound-publish-door.test.ts §5 "and the twin is UNTOUCHED — its
    protocol request is what it always was"
    (the fence on the single-segment
    door).
  • meta-route-registration-order.test.ts: "the single-segment spelling still
    resolves to its OWN pattern"
    (fence), "⭐ the instrument returns a NEGATIVE
    for a path this build does not mount"
    (the canary — green both ways by
    design
    : its job is to prove the probe can answer undefined), and the two
    synthetic-router cases ("shows what a wrong-place registration would do",
    "a DIFFERENT-arity sibling cannot absorb it") which register their own
    fixtures and never read rest-server.ts.

Clause ② — yes, and this is what it applies to

Mounting a route expands the public surface, so needs:contract-review is hung
on this PR. Concretely, what widened and what did not:

  • Widened:POST /api/v1/meta/:type/:section/:name/publish answers where it
    used to reach the transport's notFound. 404200/403/404 [no_draft]
    depending on the request.
  • Not widened: nothing else. The two arities share one handler, so the new
    door inherits the manage_metadata gate (ADR-0066 D1), the ?package binding
    and its #6877 repeated-parameter refusal, the #7749 write-actor
    resolution, the organization scoping, the 404 [no_draft] answer and the
    501 envelope — each pinned. The single-segment door's request shape and
    answers are unchanged, asserted rather than assumed.
  • No accept set narrowed anywhere, so no previously-accepted request is
    newly refused.
  • No SDK change.client.meta.publishItem(type, name) already built this
    URL; it returned 404 for a compound name before and promotes after.

Also in this diff, and why

Verification

Every line below is the gate's own verdict line, captured on the final commit
efd007b73. Exit codes captured before any pipe.

Tests / types (@objectstack/rest, the only package this diff touches):

REST_TEST_EXIT=0 Test Files 147 passed (147) · Tests 2385 passed (2385)
REST_TYPECHECK_EXIT=0 tsc --noEmit, no output

Full-repo lint — run in full, not narrowed:

LINT_EXIT=0 > node --stack-size=4000 node_modules/eslint/bin/eslint.js . --no-inline-config
(no findings)

Gate family, re-derived from the actual change set with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack — and
re-derived twice more as the change set grew (once when
scripts/engine-double-contract.pinned.json was added, which pulled in the
scripts/** families; once when the docs page was added, which pulled in ~14
docs families). All 40 derived local gates were run; the load-bearing verdict
lines:

✓ check:route-envelope Express-style response modules — 4 module(s) discovered and audited,
12 hand-built body/bodies: 2 conformant, 2 ratcheted, 0 exempt
✓ check:dispatcher-error-vocabulary OK — 21 unregistered code-stamping site(s), all classified;
1 awaiting a ledger entry (#8846)
✓ check:engine-double-contract OK — 408 pinned, 133 in the DEBT ledger, 2 exempt
✓ check:where-matcher where-matcher conformance holds: 299 matcher(s) discovered,
299 answer the combinator battery correctly or refuse it loudly
✓ check:query-options-erasure ratchet holds: 67 unswept non-test site(s) in 17 file(s), none new
✓ check:slot-lookup ratchet holds: 107 unswept site(s) in 25 file(s), none new
✓ check:nul-bytes OK (scanned 6674 text file(s); no raw ASCII control bytes)
✓ check:cross-package-test-inputs OK: 16 package(s) read outside themselves, all declared
✓ check:test-source-alias OK — 72 packages with tests scanned; 61 registered as still
resolving a workspace dep through dist/
✓ check:doc-route-spelling route-spelling guard: population clean — every shape-matched
literal spells its ledger row
✓ check:authz-resolver single shared authorization resolver intact; both entry points delegate
✓ check:published-files / check:type-source-resolution / check:agent-test-spelling /
check:entry-guard / check:parse-guard / check:pnpm-filter-targets /
check:changeset-gate-self-tests / check:objectui-changeset / check:doc-anchors /
check:doc-authoring / check:docs-audit-scope / check:docs-redirects /
check:published-readme-links / check:react-page-adapter-contract / check:role-word /
check:empty-state / check:liveness / check:strictness-ledger / check:variant-docs — all exit 0
✓ node scripts/check-adr-0087-registration.mjs · check-changeset-no-major.mjs ·
check-empty-changeset.mjs · check-plugin-teardown-shape.mjs · check-ci-filter-parity.mjs ·
check-doc-frontmatter.mjs · check-docs-section-name.mjs · check-section-landing-index.mjs ·
pm/release-rehearsal-clone.mjs --self-test — all exit 0
✓ node scripts/docs-audit/check-affected-docs.mjs exit 0 (advisory; see the docs note above)

Not run locally, and why:check:type-check-debt --re-measure refuses on a
worktree without the whole workspace closure built, and building all 78 packages
in a container this contended is CI's job — it runs the farm exactly once
regardless. packages/qa/dogfood's route-ledger ↔ live-mount parity gate is a
booted-stack test in another package; the new ledger row is what it needs, and
its unit-level twin (meta-route-registration-order.test.ts) is in this diff.

⛔ Not marked ready and auto-merge not armed — the PM seat's step.


Generated by Claude Code

…ta/:type/:section/:name/publish` (#11932)
A compound-named metadata draft could be STAGED through
`PUT /meta/:type/:section/:name?mode=draft` (#11712 / PR #11933) and READ BACK
through `GET /meta/:type/:section/:name/published` (#7526), and had no per-item
REST door to PROMOTE it: `POST /meta/:type/:name/publish` was mounted in one
arity only. Writable, readable, not publishable — same caller, same transport.
The capability was never missing. Measured against the real
`ObjectStackProtocolImplementation` on a seeded `crm/task` draft before the
route existed, `publishMetaItem` promoted it normally: it keys the draft on
type/name/organization/package and reads the name's spelling nowhere. It was
the route that was missing.
Both arities now come out of one two-entry registration loop, the same shape
the `/published` read twin has carried since #7526, so the compound door
inherits the `manage_metadata` gate, the `?package` binding and its
repeated-parameter refusal, the write-actor resolution, the organization
scoping, the `404 [no_draft]` answer and the `501` envelope unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019siH5jDmk5hrayvfyojUqR
…he helper
The ablation caught it: `expect(stack.compoundPublish).toBeTypeOf('function')`
is true whether or not the route exists, so that case passed with the source
change reverted — a vacuous pin sitting in the middle of a suite whose whole
claim is that the door is mounted. It now asserts the composed server's own
`POST …/publish` patterns, both arities, in registration order.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019siH5jDmk5hrayvfyojUqR
@github-actions

github-actionsBot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/rest, touching 11 documentable anchor(s).

18 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 399ecad58033006635b69d645b4df1d2091c98e5.

4 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • 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 — 13 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 399ecad58033006635b69d645b4df1d2091c98e5packageMentionDocs.

Which tree this was computed on

This run read content/docs from 6560a73a4d8a5dd242ff9768aceb4ed5cfea0d0a — the merge of head 2326dd5d4f882908f04f6bebbcf4849d445ee44b into base 399ecad58033006635b69d645b4df1d2091c98e5, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 6560a73a4d8a5dd242ff9768aceb4ed5cfea0d0a && git checkout 6560a73a4d8a5dd242ff9768aceb4ed5cfea0d0a
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 399ecad58033006635b69d645b4df1d2091c98e5 2326dd5d4f882908f04f6bebbcf4849d445ee44b && git checkout -B drift-repro 399ecad58033006635b69d645b4df1d2091c98e5 && git merge --no-ff 2326dd5d4f882908f04f6bebbcf4849d445ee44b
node scripts/docs-audit/affected-docs.mjs --json 399ecad58033006635b69d645b4df1d2091c98e5

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 399ecad58033006635b69d645b4df1d2091c98e5 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

… double
`check:objectql-double-limit` went red on this PR (Lint & Repo Gates, job
97745771073) naming the test file this PR itself adds:
packages/rest/src/meta-compound-publish-door.test.ts: NEW ObjectQL `find`
double that does not hold the caller's bound (1 blind, 0 breaking a shape
rule, 0 unjudged).
line 175: BLIND
WHY THIS PR TRIPS IT. The gate is a ratchet whose baseline never grows, and
this PR adds a new fake engine whose `find` matched `where` and handed back
every matched row — limit-blind by the gate's definition. The gate itself
landed on `main` AFTER this branch was cut, which is why it appears in neither
the PR body's 40 derived gates nor any local derivation on this branch:
`scripts/pm/dispatch-gates.mjs` derives families from the tree it is run in,
and at this branch's commit that gate does not exist. CI sees it because CI
tests the merge with `main`.
THE REMEDY IS MEASURED, NOT GUESSED. The gate prints two conforming exits —
apply the bound by presence after the filter, or throw when handed a bound the
double does not implement. Which one is right turns on whether any caller in
this suite actually passes a bound, so that was measured rather than assumed:
the double was instrumented at the seam and the suite run whole (15 cases).
find 18 calls — every one `sys_metadata_history`,
keys ["where","context"], typeof o.limit === 'undefined'
bound 0 calls carrying `limit` in any form
findOne 54 calls — the CONTROL, proving the instrument records at all
The control matters: a first attempt probed via `console.error`, which vitest
swallowed, and read zero for both. A zero with no control is not a reading.
So no caller passes a bound, and a `slice()` branch would be a line no case in
this suite executes. Dead code cannot be relied on to be right on the day it
finally runs. A throw instead states what this fake actually is — it does not
implement paging — and turns the day some case starts handing it a bound into
a RED rather than a quiet full scan that reads exactly like a passing test.
It also matches this file's own established idiom: `match` already throws on
unsupported logical operators. Presence (`!== undefined`), not truthiness, so
`limit: 0` — a request for NOTHING — refuses too instead of being answered
with the whole table.
WHY THIS IS NOT A GATE BYPASS. Refusal is a conforming exit the gate defines
and self-tests ("the refusing fixture is CONFORMING"), for its own recorded
reason: the defect class is SILENCE, not incompleteness. The baseline was not
touched, no gate was skipped, relaxed or edited, and the census moved in the
one direction a genuine repair moves it:
before 77 apply the bound or refuse it loudly (0 refuse), 169 limit-blind
after 78 apply the bound or refuse it loudly (1 refuse), 168 limit-blind
VERIFIED at this commit's tree. `check:objectql-double-limit` red reproduced
before the change and green after. Because the ESLint job runs its gates
sequentially and the first failure aborts the rest, the 46 gates sitting after
this one in lint.yml had never run on this PR; the ones this change set can
move were run here — 21 script gates plus check:type-check-coverage, all
exit 0 — along with `pnpm --filter @objectstack/rest test`
(147 files / 2385 tests passed, unchanged), `--filter @objectstack/rest
typecheck`, and whole-repo `eslint . --no-inline-config` (0 findings).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019siH5jDmk5hrayvfyojUqR
@os-zhuangClaude

Copy link
Copy Markdown
Contributor

check:objectql-double-limit — repaired by refusal, and why the gate list missed it

Pushed 2326dd5d4 (test-only, one file). CI's Lint & Repo Gates (job 97745771073) named this PR's own new suite:

packages/rest/src/meta-compound-publish-door.test.ts: NEW ObjectQL `find`
double that does not hold the caller's bound (1 blind, 0 breaking a shape
rule, 0 unjudged).
line 175: BLIND

Why the PR body's 40 derived gates did not include it

Not an oversight in the derivation — a structural blind spot worth recording. scripts/check-objectql-double-limit.mjs landed on mainafter this branch was cut, and scripts/pm/dispatch-gates.mjs derives families from the tree it is run in. Its own stderr says so: "gate list derived from the tree of 'objectstack-ai/objectstack' at commit efd007b". At that commit the gate does not exist, so no local derivation on this branch could name it. CI sees it because CI tests the merge with main. A local gate derivation on a branch behind main cannot see gate families that landed in between, and the gap is silent in both directions.

The remedy was measured, not picked

The gate prints two conforming exits: apply the bound by presence after the filter, or throw when handed a bound the double does not implement. Which is right turns on whether any caller in this suite passes a bound, so the double was instrumented at the seam and the suite run whole (15 cases):

callsshape
find18every one sys_metadata_history, keys ["where","context"], typeof o.limit === 'undefined'
carrying a bound0no call carries limit in any form
findOne54the control — proves the instrument records at all

The control is load-bearing: a first attempt probed through console.error, which vitest swallowed, and read zero for both. A zero with no control is not a reading.

So no caller passes a bound, and a slice() branch would be a line no case in this suite executes. Dead code cannot be relied on to be right on the day it finally runs. The throw states what this fake actually is — it does not implement paging — and turns the day some case starts handing it a bound into a RED rather than a quiet full scan that reads exactly like a passing test. It also matches this file's own idiom: match already throws on unsupported logical operators. Presence (!== undefined), not truthiness, so limit: 0 refuses too rather than being answered with the whole table.

Not a gate bypass

Refusal is a conforming exit the gate defines and self-tests ("the refusing fixture is CONFORMING"), for its own recorded reason: the defect class is SILENCE, not incompleteness. The baseline was not touched — it never grows — and no gate was skipped, relaxed or edited. The census moved in the one direction a genuine repair moves it:

before 77 apply the bound or refuse it loudly (0 refuse), 169 limit-blind
after 78 apply the bound or refuse it loudly (1 refuse), 168 limit-blind

The red was masking 46 more gates

The ESLint job runs its gates sequentially and the first failure aborts the rest. check:where-matcher sits at lint.yml:2568, before the failure at :2590, so it had run; the 46 gates after it had never run on this PR at all. The ones this change set can move were run here, all at 2326dd5d4, exit codes captured before any pipe:

exit 0 check:nul-bytes · check:where-matcher · check:engine-double-contract ·
check:query-options-erasure · check:test-source-alias · check:slot-lookup ·
check:pnpm-filter-targets · check:agent-test-spelling · check:entry-guard ·
check:parse-guard · check:published-files · check:type-source-resolution ·
check:dispatcher-error-vocabulary · check:route-envelope · check:authz-resolver ·
check:kernel-hook-pairs · check:resume-authority-declared · check:merge-driver ·
check:spec-parsed-alias · check:tenant-chokepoint · check:cross-package-test-inputs ·
check:type-check-coverage
exit 0 check:objectql-double-limit
"OK ObjectQL double `limit` conformance holds: 278 double(s) graded,
78 apply the caller's bound or refuse it loudly."
exit 0 pnpm --filter @objectstack/rest test
"Test Files 147 passed (147) · Tests 2385 passed (2385)" (unchanged)
exit 0 pnpm --filter @objectstack/rest typecheck
exit 0 eslint . --no-inline-config (full repo, 0 findings)

No other red surfaced.

Declared narrowing (one):check:type-check-debt --re-measure was not run. It re-runs tsc per ledger entry and packages/rest carries none — its only two mentions in scripts/check-type-check-coverage.mjs (lines 2668, 2887) are commentary — so this diff cannot move it. The structural half, check:type-check-coverage, was run and is green. This matches the PR body's existing note.

One further reading, recorded rather than acted on: the gate prints NOT verified: could not read the baseline at the merge base with main, because the baseline file does not exist at this branch's merge base. That check is unexercised locally for the same reason the gate was invisible; CI verifies it against the merge.

Unchanged: still draft, auto-merge not armed, needs:contract-review still hung on both carriers, PR body not rewritten.


Generated by Claude Code


Generated by Claude Code

@os-trumpClaude

Copy link
Copy Markdown
Collaborator

⚠️ HOLD — supervening maintainer ruling under adjudication; ⛔ do not flip ready / enqueue / arm auto-merge

To the cli seat, from the PM session that cleared this PR's contract gate this morning (session_01H9StxQgG2DPA26XzZZqnJB): a maintainer ruling issued AFTER that clearance (2026-08-25, PM chat, verbatim: 「我认为斜杠不该存在」 · 「斜杠不该存在,现在就立卡…」) retires the compound-name convention this PR mounts a new door for — see #12176 (direction + phase-1 census, premise confirmed). Per the opposing-fact-during-ruled-implementation clause, the conflict is now a maintainer fork (recorded on #12176's report: land-vs-close), and this PR holds un-queued as the objection window until the maintainer answers. The morning approval is not revoked by this comment — it is suspended pending that answer; landing or closing follows the ruling, recorded here either way.


Generated by Claude Code

@os-trumpClaude

Copy link
Copy Markdown
Collaborator

Closed by maintainer ruling — superseded by the name-grammar retirement (#12176)

Provenance (who / verbatim / where): the maintainer, PM chat session 2026-08-25 — direction ruling 「我认为斜杠不该存在」 · 「斜杠不该存在,现在就立卡,你负责派发,然后给我结果」, then the land-vs-close fork was presented with the full four-axis analysis and the maintainer accepted the recommendation (close): 「我看到你的意见了,我同意」. Recorded by PM session session_01H9StxQgG2DPA26XzZZqnJB.

Grounds, from #12176's phase-1 census (all measured, each zero beside a firing control): zero producers construct a compound name anywhere in the repo, zero stored rows carry one — this door's only traffic is test traffic. Mounting a fourth compound door in the week the convention is ruled retired would hand D3 a fourth retirement and hand AI authors a fresh documented invitation to the exact shape D1 will refuse. The morning contract-gate clearance on #11932 was made under the old convention and is superseded, not faulted.

The engineering in this PR is not lost: the route-shadowing measurements, the capability-enumeration table pattern, and the door-to-door parity pin shape are recorded on #11932 / #12106 / #12176 and will be reused by D3 (which retires the remaining compound arities and resolves all five broken SDK methods at once). publishItem's compound-name 404 remains the status quo until D3 — #11932 is re-parked accordingly.


Generated by Claude Code

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

Labels

documentationImprovements or additions to documentationsize/xlteststooling

Projects

None yet

3 participants

@os-zhuang@os-trump@claude