Repository navigation
#4463 运行时发布门在 host-config 拓扑(environmentId 未绑定)上整体不跑——#5086 只把 code-only 拒绝移出了同一条短路 #6710
Description
Activity
Triage:
needs-user-decision+domain:metadata+target:v17.- Anchors re-verified on
origin/main@cfeb9a0:packages/metadata-protocol/src/protocol.ts:2426—assertRuntimeAuthoringRulesstill opens with theenvironmentId === undefinedearly return;packages/cli/src/commands/serve.tsauto-register branch still bootsnew ObjectQLPlugin()with no environmentId (the suspect topology's in-repo producer). Premise live. - Why decision-box, not queue: widening the gate's activation changes enforcement for all 26 shared
AUTHORING_RULESon an end-user surface — and the [转移] 发布期护栏:多组织下「平台级 + schedule + create_record + 未显式 organization_id」拒绝——#6155 Q3=A 裁决的 lint 半边 #6285 dispatch already reserved exactly this call ("STOP and escalate, do not widen the gate's activation surface unilaterally"). The fork the maintainer owns: (1) re-key activation fromenvironmentIdto topology/source per the metadata: allowRuntimeCreate:false is not enforced — PUT /meta creates job and agent items the registry declares code-only #5086 pattern, (2) make host-config assemblies bind an environmentId so the carve-out's premise holds again, or (3) close if the topology no longer reproduces. The factual repro (does a host config still assemble without environmentId?) is the first step of whichever execution card follows the ruling — the in-repo producer above suggests it still does, but that is one hop short of a boot-level repro. - Release board:
target:v17on ground ② — on the affected topology the [runtime/metadata] 作者时规则只存在于 CLI:Studio/REST/MCP 的运行时授权面是第四扇门,26 条规则一条不跑——#4409 修完后最大的敞口 #4463 gate, described at its own filing as the ONLY authoring gate for Studio/MCP authors, runs zero of its rules on the end-userPUT /api/v1/meta/*surface. If the repro shows the topology is gone, pull the tag together with option 3. - Dedup: repo-scoped
assertRuntimeAuthoringRulessearch — only [转移] 发布期护栏:多组织下「平台级 + schedule + create_record + 未显式 organization_id」拒绝——#6155 Q3=A 裁决的 lint 半边 #6285 (the verification source, correctly landed without touching activation) and this card. metadata: allowRuntimeCreate:false is not enforced — PUT /meta creates job and agent items the registry declares code-only #5086 (closed) fixed the code-only slice only; An org-scoped overlay row reaches the process-wide SchemaRegistry on unscoped kernels — both the write-through and the read hydration gate only onenvironmentId, never on org #6602 is the org-axis sibling, different key. Clean.
本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
- Anchors re-verified on
Maintainer ruling (2026-08-08): conditional ruling — premise first, then direction 1 (Option A).
Premise (falsifiable — verify before implementing): the host-config topology still assembles without environmentId. A boot-level reproduction on current origin/main is the dispatch's first deliverable (the serve.ts auto-registration branch is the suspected in-repo producer). If the premise is falsified — the topology no longer exists — report the fork and close per direction 3; do not force the implementation, and do not silently fall back to another option.
If the premise holds: Option A — re-key the gate's activation from the environmentId proxy to an explicit topology/origin judgment, extending the #5086 pattern, so the package-author own-channel carve-out gets its own expression. The 26 rules' test posture migrates with it.
Rationale (three-axis review):
- Business: if reproduced, an end-user-facing PUT /api/v1/meta/* surface runs zero of the 26 authoring rules on that topology — the v17 tag is earned.
- Long-term: environmentId-undefined is a proxy key for "control plane", and this card is itself the proof that proxy keys lie. Direction 2 would re-prop the proxy, and every future assembly variant reopens the hole; A writes the semantic down once.
- AI-error containment: the 26 authoring rules are the primary anti-AI-error surface; a topology that silently skips them all is the largest single hole.
target:v17 stays while the premise stands; if the reproduction falsifies it, the closure takes the tag with it.
State: needs-user-decision removed; pm:queue added — metadata lane.
Maintainer directive (verbatim, covering this batch of 6 inbox cards): 「接受你的建议」. Recorded by PM session session_01JaVVMrSxt7Tgi1uwEuDtH7.
Generated by Claude Code
Claim: PM loop round 4 (
domain:metadataseat, sticker #6367)
Session:session_01W6bLax4KMrSfnE1ydFU8Dw
Branch:claude/issue-6710-gate-activation-rekey
Worktree:objectstack-issue-6710
Domain:domain:metadata
File surface:packages/metadata-protocol/src/protocol.ts(assertRuntimeAuthoringRulesactivation:2426) +runtime-authoring-gatetests; boot-level reproduction readspackages/cli/src/commands/serve.tsandpackages/objectql/src/plugin.ts(read/measure); if Option A's topology signal requires a source change outsidemetadata-protocol, declare file-by-file in the PR body and keep it minimal — a new public contract shape is a STOP.
Serial constraints cleared: #6285/PR #6708 (the gate's newest rule) and #6602/#6621 all MERGED. Maintainer conditional ruling of 2026-08-08 governs: premise first (boot-level repro of the no-environmentId host-config topology; falsified ⇒ close per direction 3 with the measurement, take the v17 tag off with it), then Option A (re-key activation from the environmentId proxy to an explicit topology/origin judgment extending #5086's pattern; the 26 rules' test posture migrates). Round-4 siblings: #6780 (:7829), #6640 (~:1045) — disjoint regions.
Generated by Claude Code
Dev report (session
session_01W6bLax4KMrSfnE1ydFU8Dw, branchclaude/issue-6710-gate-activation-rekey): premise HOLDS — boot-level reproduction below. But Option A cannot be implemented within the dispatch's declared scope: every candidate topology signal is either a NEW plugin option (the dispatch's explicit STOP), a verdict move on the control-plane topology (forbidden by the same dispatch), or another proxy key (the exact mistake the ruling forbids). Escalating with the measurement + the fork.1. Premise: CONFIRMED at boot level (origin/main @
6de592c43)Not a source read — an actual kernel boot of the serve.ts host-config branch's construction (
ObjectKernel+new ObjectQLPlugin(), no options,serve.ts:1000), then the end-user publish thatPUT /api/v1/meta/flow/leave_approvalfunnels into (runtime/src/domains/meta.ts:271,rest/src/rest-server.ts:5126), using #4463's own measured body (the broken-CEL approval flowrecord.owner ==):=== A. host-config topology — serve.ts:1000 `new ObjectQLPlugin()` === protocol.environmentId : undefined PUT /meta verdict : {"outcome":"THROWN","message":"[ObjectQL] No driver available for object 'sys_metadata'"} #4463 GATE FIRED? : falseThe throw is from the engine's persistence layer, not the gate — i.e. execution ran straight past
assertRuntimeAuthoringRules(which throws 422INVALID_METADATAbefore any engine call) and into the write. The bare repro attaches no driver, which is why persistence is where it stops; the gate's verdict is the measured fact.Route to the branch re-verified:
isHostConfig(config)(anyplugins:entry with aninit) makesshouldBootWithLibrary()returnfalse(cli/src/utils/plugin-detection.ts), soserve.ts:800skips the standalone stack (the one that bindsproj_local) and falls to the lightweight assembler atserve.ts:996-1003.examples/app-showcase/objectstack.config.ts:130is exactly that shape (plugins: [new ConnectorOpenApiPlugin(), ...]) — the flagship showcase is on the affected topology.2. Why Option A stops here
The ruling requires the carve-out to stay AND the host-config topology to become gated. Those two topologies must therefore be told apart. Measured: nothing in either repo tells them apart today.
genuine control plane host-config topology environmentIdundefined undefined assembly assembleMetadataProtocolassembleMetadataProtocolserves /api/v1/meta/*yes (cloud control-plane-preset.ts:277note)yes Every derivable signal I evaluated is a proxy of the same class the ruling condemns: delegated-vs-built-in mount (
createMetadataProtocolPlugin()vsObjectQLPlugin's built-in — cloud'sobjectos-stack.ts:223host router uses the delegated one too),OS_MODE=cloud(an operator env var that would let a deployment switch off an end-user guardrail), or presence of control-plane service plugins. So the signal has to be minted and stated by an assembly — and the only assembly that can truthfully state "package author's own channel" is cloud'scontrol-plane-preset.ts, outside this PR.Two supporting measurements the decision should have:
- The carve-out has no ADR text of its own. ADR-0005 §"Whitelist enforcement" says: "Single-kernel deployments (no
projectId) keep their existing behaviour (any type writable)" — a back-compat clause about the overlay whitelist, not a statement that unscoped kernels are the package author's channel. metadata: allowRuntimeCreate:false is not enforced — PUT /meta creates job and agent items the registry declares code-only #5086's comment already noticed this ("That sentence predatesallowRuntimeCreateand speaks only of the overlay list"). The "package-author own-channel" reading exists only in code comments. - The carve-out's only genuine consumer is the cloud control plane. Every other unscoped
new ObjectQLPlugin()in either repo is an end-user-serving kernel or a test harness:cli/commands/serve.ts:1000(the hole),objectql/kernel-factory.ts:38,verify/harness.ts:271,plugin-dev/dev-plugin.ts:302,scripts/analytics-reconcile/boot.ts,examples/app-todo/test/*; cloud:control-plane-preset.ts:99(genuine) andobjectos-stack.ts:175(host router, in-memory driver).
3. The fork (three-axis analysis in the dev report; summary here)
- A1 — opt-in carve-out, fail-safe default. Gate active by default; the control plane declares itself (
authoringChannel: 'package-author'oncreateMetadataProtocolPlugin()/ObjectQLPlugin). Writes the semantic down once; an assembly that forgets it gets MORE enforcement, never less. Costs a new plugin option (dispatch STOP) and a cross-repo window like ADR-0076 Step 2 (ADR-0076: remaining objectql/protocol layering work (cross-repo window) #2462) — until cloud lands it the control plane runs gated, softened by the existingOS_ALLOW_UNLINTED_METADATA_WRITEShatch ([runtime/metadata] 作者时规则只存在于 CLI:Studio/REST/MCP 的运行时授权面是第四扇门,26 条规则一条不跑——#4409 修完后最大的敞口 #4463 D4). My recommendation. - A2 — opt-in enforcement, fail-open default. Only the in-repo end-user assemblies stamp an "environment channel" origin. No cloud change, no verdict moves — but still a new option, and every future assembly that forgets it silently reopens this exact hole.
- A3 — retire the topology key entirely (the most literal extension of metadata: allowRuntimeCreate:false is not enforced — PUT /meta creates job and agent items the registry declares code-only #5086: the declaration decides, not the topology). No new option, no cross-repo window, lands wholly inside
packages/metadata-protocol; only the server-statedsourcecarve-outs and the draft rule survive. Requires re-opening the ruling's "the carve-out stays" clause, which I am not doing unilaterally.
No PR opened, per the dispatch's STOP rule. Worktree torn down;
target:v17untouched (the premise stands, so the tag is earned).
Generated by Claude Code
- The carve-out has no ADR text of its own. ADR-0005 §"Whitelist enforcement" says: "Single-kernel deployments (no
PM: moved to
needs-user-decision(metadata seat, sessionsession_01W6bLax4KMrSfnE1ydFU8Dw)The dev's measurement (comment above) confirmed the premise at boot level: a host-config kernel assembled exactly as
serve.ts:1000builds it yieldsenvironmentId === undefined, and #4463's broken-CEL approval flow runs straight pastassertRuntimeAuthoringRulesinto persistence — the flagship showcase's own boot shape is on the affected topology. The Option-A implementation then hit the dispatch's own STOP rule: no signal exists today that separates the control-plane topology (whose carve-out your ruling keeps) from the host-config topology (which your ruling gates) — both haveenvironmentId === undefinedand share the same assembly seam — so the signal must be minted, which requires a new plugin option. No PR was opened;target:v17stays.The decision — which expression should the carve-out get:
- A1 — opt-in carve-out, fail-safe default (dev's recommendation; I concur). Gate active by default on every kernel; the control-plane assembly declares itself via a new option (e.g.
authoringChannel: 'package-author') plumbed throughassembleMetadataProtocol. Costs a cross-repo window withcloud(control-plane-preset.ts must set it — same coordination ADR-0076 Step 2/ADR-0076: remaining objectql/protocol layering work (cross-repo window) #2462 already ran); until cloud lands it, the control plane runs gated in the safe direction, softened by the existingOS_ALLOW_UNLINTED_METADATA_WRITEShatch. An assembly that forgets to declare gets MORE enforcement, never less. - A2 — opt-in enforcement, fail-open default. Only end-user assemblies stamp an "environment channel" origin; no cloud change. But every future assembly that forgets to stamp silently reopens this exact hole — the fail-open shape that produced this card.
- A3 — retire the topology key entirely (most literal metadata: allowRuntimeCreate:false is not enforced — PUT /meta creates job and agent items the registry declares code-only #5086 extension). No new option, no cross-repo window — but it permanently gates the control-plane topology, contradicting your "the carve-out itself is legitimate and stays" clause, so it needs that clause re-opened first. Honest second choice if you want to avoid the cross-repo window.
Two supporting measurements: the carve-out has no ADR text of its own (ADR-0005's clause covers only the overlay whitelist), and its only genuine consumer in either repo is cloud's
control-plane-preset.ts:99. Secondary (only if A1/A2): where the boot-level pin lives — dev recommends the split: posture matrix stays inmetadata-protocol's existing gate test, plus one assembly-wiring pin inpackages/objectql(the dependency cycle keeps the assembly pin out ofmetadata-protocol).
Generated by Claude Code
- A1 — opt-in carve-out, fail-safe default (dev's recommendation; I concur). Gate active by default on every kernel; the control-plane assembly declares itself via a new option (e.g.
os-project-manager commented
on Aug 9, 2026 CollaboratorMore actionsMaintainer ruling (2026-08-09): A1 — opt-in carve-out, fail-safe default.
pm:queue.The gate is active by default on every kernel; the package-author own-channel carve-out becomes an explicit declaration (e.g.
authoringChannel: 'package-author') plumbed throughassembleMetadataProtocol, which only the genuine control-plane assembly sets. The 26 rules' test posture migrates with it, split as the dev recommended: posture matrix inmetadata-protocol's existing gate test plus one assembly-wiring pin inpackages/objectql.Rationale (three-axis): the boot-level repro stands — the flagship showcase's own topology runs zero of the 26 authoring rules on an end-user
PUT /api/v1/meta/*surface, so the business half is not hypothetical. Long-term, this card is itself the proof that proxy keys lie; A1 writes the semantic down once, where A2's forget-to-stamp failure mode silently reopens exactly this hole on every future assembly. For AI-error containment the asymmetry is decisive: an assembly that forgets the declaration gets MORE enforcement, never less. A3 is acknowledged as the honest no-new-option alternative but is rejected because it would overturn the standing "the carve-out stays" clause.Cross-repo window, accepted: until
cloud'scontrol-plane-preset.tsdeclares the channel, the control plane runs gated in the safe direction, softened by the existingOS_ALLOW_UNLINTED_METADATA_WRITEShatch — same coordination shape as ADR-0076 Step 2 (#2462). The cloud repo is outside this session's scope; the implementing PR records the one-line cloud follow-up in its body. New public plugin-option shape: the PR body must declare it explicitly (the prior dispatch's STOP is hereby answered — the option is authorized).Maintainer directive (verbatim, covering all 25 decision-inbox cards this round): 「全部接受」. Recorded by PM session session_01LGRN2cSRfggfX9B2L83bQc. Veto window open — comment to overturn.
Generated by Claude Code
Re-dispatching under the A1 ruling — PM loop round 6 (
domain:metadataseat, sticker #6367).Session:
session_01W6bLax4KMrSfnE1ydFU8Dw(same seat/session that claimed this card at 2026-08-08 21:41Z — that claim stands, this is its continuation, not a re-claim)
Branch:claude/issue-6710-authoring-channel-declaration
Worktree:objectstack-issue-6710
Base:origin/main@68feaadd6(satisfies the ≥47a4e676floor this lane requires forprotocol.tswork)The 2026-08-09 05:41Z ruling answers the STOP that ended round 4: A1 — the gate is active by default on every kernel; the package-author own-channel carve-out becomes an explicit declaration plumbed through
assembleMetadataProtocol, which only the genuine control-plane assembly sets. The new public plugin-option shape is authorized. Test posture splits as recommended: posture matrix stays inmetadata-protocol's existing gate test, plus one assembly-wiring pin inpackages/objectql.File surface declared:
packages/metadata-protocol/src/protocol.ts(assertRuntimeAuthoringRulesactivation region ~:2426+ the assembly entry point that threads the option) andpackages/objectql(option declaration + assembly-wiring pin), plus their tests and a changeset.cloud'scontrol-plane-preset.tsis out of this repo's scope — the PR body records the one-line follow-up. Any need to touchpackages/specgets a cross-seat declaration on #6298 first (precedent #6532/#5586); a second new public contract shape beyond the authorized option is a STOP.Round-6 sibling in flight: #6190 on the
saveMetaItemwrite-gate region (~:7xxx) — region-disjoint from this card's activation region; merge-main discipline applies on both.
Generated by Claude Code
os-dev report
Session
session_01W6bLax4KMrSfnE1ydFU8Dw, branchclaude/issue-6710-authoring-channel-declaration, baseorigin/main@68feaadd6. Draft PR: #6971. Implements the A1 ruling; no re-litigation, no substitution.1. What I measured
Premise re-verified at boot level on this base — it HOLDS. Not a source read: a real
ObjectKernelbooted withnew ObjectQLPlugin()(byte-for-byte whatserve.ts'sconfig.objects && !hasObjectQLbranch constructs), then #4463's own measured body (the broken-CEL approval flowrecord.owner ==) pushed throughsaveMetaItem:PROBE environmentId : undefined PROBE PUT /meta verdict : {"outcome":"THROWN","message":"[ObjectQL] No driver available for object 'sys_metadata'"} PROBE #4463 GATE FIRED? : falseThe throw is the engine's persistence layer, not the gate — the gate throws 422
INVALID_METADATAbefore any engine call, so execution had already run past it. Identical to round 4's measurement; nothing intervening closed the topology. Producer still present atpackages/cli/src/commands/serve.ts:996-1001.PM mechanism assumptions, checked:
# assumption verdict 1 premise proven at boot level, re-verify confirmed, unchanged on 68feaadd62 authoringChannelis an e.g., semantics must be "I am the package author's channel"followed — kept the name, made it a channel-name union rather than a boolean (see §2) 3 option threads plugin → assembleMetadataProtocol→ activationcorrect as described; wiring found exactly that shape, no deviation 4 environmentIdkeeps its other jobs; only this activation re-keyscorrect — the two are cleanly separable; no STOP needed. Now pinned by a dedicated test 2. What I changed, file by file
file change packages/metadata-protocol/src/protocol.tsnew exported type MetadataAuthoringChannel = 'environment' | 'package-author';authoringChannelfield; 4th constructor param defaulting to'environment'; the activation line becomesif (this.authoringChannel === 'package-author') return;packages/metadata-protocol/src/plugin.tsMetadataProtocolPluginOptions.authoringChannel; newAssembleMetadataProtocolOptions;assembleMetadataProtocolgains a 4th options-bag param and resolves?? 'environment'at the seampackages/metadata-protocol/src/index.tsexports the new type + options interface packages/objectql/src/plugin.tsObjectQLPluginOptions.authoringChannel; private field; forwarded to the assemblypackages/metadata-protocol/src/protocol.runtime-authoring-gate.test.tsthe 26 rules' posture matrix (6 rows) + 3 supporting cases; the old carve-out case re-spelled to declare the channel packages/metadata-protocol/src/protocol.platform-schedule-org-gate.test.tsone fixture re-spelled + one companion added — see below packages/objectql/src/plugin.authoring-channel.test.tsnew: the assembly-wiring pin (6 cases) .changeset/authoring-channel-declaration.mdminor for both packages Two design choices worth the maintainer's eye:
- A channel-name union, not
authoringChannel?: 'package-author'alone and not a boolean.skipAuthoringRules: truewould be the same bytes with the opposite meaning — a kill switch any assembly could reach for to make a red publish go away. A caller must claim to be the package author. There is deliberately no env-var fallback (unlikeskipSchemaSync), so a deployment cannot switch an end-user guardrail off from outside the code; the per-writeOS_ALLOW_UNLINTED_METADATA_WRITEShatch remains the only softening, and it degrades rather than silences. - The declaration alone decides;
environmentIdis not AND-ed back in. A hybrid (channel === 'package-author' && environmentId === undefined) would be marginally stricter but would re-admit the retired proxy into the activation judgment, and a control plane that later gained a row scope would silently flip posture. That posture is pinned explicitly in the matrix with its reasoning, so the rule reads one way only.
Fixture sweep beyond the edited package (the consumption-radius rule): one hit,
protocol.platform-schedule-org-gate.test.ts(#6285), which carried its own copy of the carve-out pin spelled with the retired key. Disposition re-spell, not delete — the case's subject is intact, only its expression changed. It also gained a companion case, because #6285's guardrail is one of the 26 and its reach widened too; without it the file would assert only the side that stayed the same.Not touched: the
saveMetaItemtwo-tier write-gate region (#6190 in flight),packages/spec,content/docs/releases/, and thecloudrepo.3. Reverse verification — predictions vs results
Predictions written down before running, then the activation line alone reverted to
if (this.environmentId === undefined) return;with plumbing and tests left in place. Predicted 7 red, measured 8 — one miss, recorded rather than tidied away.protocol.runtime-authoring-gate.test.ts(predicted 4 red, measured 5; the 11 pre-existing #4463 cases stayed green):case predicted measured envId undefined, channel omitted ⇒ GATED RED RED envId undefined, channel environment⇒ GATEDRED RED envId undefined, channel package-author⇒ bypassedgreen (cannot go red) green envId env_test, channel omitted ⇒ GATEDgreen green envId env_test, channelenvironment⇒ GATEDgreen green envId env_test, channelpackage-author⇒ bypassedRED (inverted) RED gated by the rules, not blanket green green D4 hatch covers the newly-gated topology RED RED does not disturb the #3050 gate green RED — MISSED Three notes the pass count would hide:
- One row cannot go red at all.
envId undefined + channel package-authoris green before and after — the old code bypasses there too, for the wrong reason. Honest coverage of the surviving carve-out, but not evidence for this change; the matrix says so in its own comment. - One row is inverted by design.
envId env_test + channel package-authorgoes red on the revert because the OLD code gates where the new code bypasses. That row is what proves the declaration is genuinely read. - The miss:
does not disturb the OTHER gateswent red, not green. Cause on inspection: its tenant leg saves a broken flow, and under the old keying the [runtime/metadata] 作者时规则只存在于 CLI:Studio/REST/MCP 的运行时授权面是第四扇门,26 条规则一条不跑——#4409 修完后最大的敞口 #4463 gate refuses it with a 422 before the OWD posture is lint-only at runtime: env can widen a packaged object's sharingModel via OS_METADATA_WRITABLE, and external≤internal (ADR-0090 D11) is never enforced on the write path #3050 gate is reached, so the observed gate list stayed empty. The red is real and informative, but I predicted green having reasoned only about the OWD posture is lint-only at runtime: env can widen a packaged object's sharingModel via OS_METADATA_WRITABLE, and external≤internal (ADR-0090 D11) is never enforced on the write path #3050 gate's own condition, not about what precedes it.
plugin.authoring-channel.test.ts: predicted 3 red, measured 3 (exactly cases 1, 3, 5). The load-bearing detail — the boot-level case failed onexpected undefined to be 'INVALID_METADATA', not on "did it throw". The unfixed build throws too (the engine's bareNo driver available, anErrorwithcodeandstatusboth undefined), so arejects.toThrow()assertion would have stayed green on the exact topology this issue is about. Every rejection-class case asserts the ADR-0112code+statuspair for that reason.Side observation: under the reverted line
pnpm --filter @objectstack/metadata-protocol buildfails its DTS step (the field becomes unread). ESM/CJS bundles emit before DTS fails, so the behavioural measurement above is still valid.4. Tests (local) and CI (per-job conclusions)
All local runs through
flock /tmp/os-heavy-verify.lockwithNODE_OPTIONS=--max-old-space-size=4096:@objectstack/metadata-protocol 65 files / 813 tests passed @objectstack/objectql 160 files / 2758 tests passed @objectstack/rest 72 files / 1131 tests passed @objectstack/runtime 115 files / 1743 tests passed @objectstack/cli 100 files / 1044 tests passed typecheck (metadata-protocol, objectql) Done, 0 errors pnpm lint (whole repo) exit 0rest/runtime/cliare in the sweep because this change widens enforcement — they boot kernels previously on the bypassed side. All green with no fixture edits needed.CI on
5901d60, per job (not the aggregate):job conclusion ESLint (carries the family gates) completed: successTypeScript Type Check completed: successBuild Core / Test Core (1-3 of 3) completed: successDogfood Regression Gate (1-3 of 3) + Dogfood Verify CLI completed: successTemporal Conformance (live PG + MySQL) completed: successCheck Changeset / Check PR Size / ADR maintainer approval completed: successFull run converged: 0 pending, 0 failures, 23 check runs. Family gates also confirmed locally before push (
check:engine-double-contract,check:driver-memory-census,check:error-code-casing,check:route-envelope,check:meta-type-normalized,check:init-service-contract,check:published-files,check:doc-authoring,check:docs-audit-scope,check:nul-bytes).5. What I could NOT verify — stated rather than omitted
- The
cloudfollow-up is unexecuted by design.control-plane-preset.ts:99needsauthoringChannel: 'package-author'; the cloud repo is out of this card's scope and I did not open it, so I am relying on round 4's inventory for the claim that it is the only genuine consumer — I did not re-enumerate it this round. Recorded in the PR body as required. - Real control-plane behaviour during the cross-repo window is untested end-to-end. I pinned that the D4 hatch works on precisely the posture the window creates (envId undefined + channel undeclared), which is the mechanism the ruling relies on — but no live control plane was booted.
- The probe used the bare kernel with no driver attached, so the pre-fix verdict stops at persistence rather than showing the broken row landing in
sys_metadata. The gate's non-firing is the measured fact; "the row would have persisted" is inference from where the throw originates. - Blast radius beyond the packages I swept. I ran metadata-protocol, objectql, rest, runtime, cli plus repo-wide lint; I did not run every service/plugin package's suite. CI's Test Core shards did, and went green.
Generated by Claude Code
Generated by Claude Code
- A channel-name union, not
- added a commit that references this issue
on Aug 9, 2026
观察到的缺口
ObjectStackProtocolImplementation.assertRuntimeAuthoringRules(packages/metadata-protocol/src/protocol.ts,#4463 的运行时发布门)第一行是:这条短路本身是既有设计且正确——它是 ADR-0005「控制面 / 包作者自有通道」的 carve-out,与其下方 #3050 authoring gate 的
if (this.environmentId !== undefined)同一口径,protocol.runtime-authoring-gate.test.ts也把它作为刻意行为钉住。本单不是要求撤掉它。问题在于
environmentId === undefined并不只意味着控制面内核。#5086 在同一文件里(saveMetaItem的 code-only 拒绝块注释)留下的原话即是证据:#5086 据此把 code-only 类型的拒绝移出了
environmentId门(「Keying authorization off a row-scoping key is what made a type-level declaration depend on deployment topology; the declaration decides it here instead」),但只移了那一处。#4463 的运行时发布门(共享AUTHORING_RULES的 26 条规则)仍留在短路后面。于是在 host-config 拓扑上,
PUT /api/v1/meta/*这个终端用户面上一条作者时规则都不跑——而 #4463 立单的理由恰恰是「对 Studio 租户 / MCP-AI 作者来说这不是四道门里较弱的一道,而是唯一的一道」。现状核验(本单只测了这些,未测的如实标出)
已实测:每条常规服务路径都绑定 environmentId,所以它们照常进门——
os devenv_local(默认)packages/cli/src/commands/dev.ts:225os startenv_local(默认)packages/cli/src/commands/start.ts:197proj_local(默认)packages/runtime/src/standalone-stack.ts:378packages/objectql/src/plugin.ts:295未实测:host-config 拓扑今天是否仍会走到无 environmentId 的装配。仓内有两处形状可疑——
packages/cli/src/commands/serve.ts:1000的config.objects && !hasObjectQL自动注册分支(new ObjectQLPlugin(),无 environmentId),以及任何objectstack.config.ts里手写plugins: [new ObjectQLPlugin()]的 host。上面引的注释是 #5086 当时的测量,不是我这轮的复测。定级前应先复现这一条:若该拓扑已随其它改动消失,本单可直接关掉;若仍在,则是一个终端用户面上的门整体失效。为什么单独立单
发现于 #6285(PR #6708)。该单的 dispatch 明确要求「若短路使护栏在关键部署形态不可达,STOP 上报,⛔ 不得擅自拓宽 gate 激活面」。核验结论是本护栏可达(多组织部署都在上表的路径上),所以 #6285 按裁决正常落地、未动激活面。但拓宽与否会同时改变全部 26 条共享规则的影响面,是独立的契约决定,不该搭在一个护栏单里顺手做。
可能的方向(不预判,留给分诊/维护者)
environmentId(一个行作用域键)改为按拓扑/来源判定,让「包作者自有通道」这个真实意图有一个自己的表达,而不是借用 environmentId;Refs:#4463(门的立单与四项裁决)、#5086(同一前提的上一次修补,只移了 code-only 一处)、#6285 / PR #6708(本次核验的来处)、#6602(同族:另一处只按
environmentId而不按 org 把关的 seam)。Generated by Claude Code