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

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

Conversation

@claude

@claude claude Bot 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.publishItem already 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 only packages/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:

file red / total
meta-compound-publish-door.test.ts 14 / 15
meta-write-door-capability-enumeration.test.ts 7 / 45
meta-route-registration-order.test.ts 4 / 13
rest-write-response-internal-fields.tripwire.test.ts 1 / 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. 404 → 200/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

claude added 2 commits August 25, 2026 08:25
…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-actions Bot 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 399ecad58033006635b69d645b4df1d2091c98e5 → packageMentionDocs.

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

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 main after 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):

calls shape
find 18 every one sys_metadata_history, keys ["where","context"], typeof o.limit === 'undefined'
carrying a bound 0 no call carries limit in any form
findOne 54 the 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

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

Labels

documentation Improvements or additions to documentation size/xl tests tooling

Projects

None yet

2 participants