Skip to content

[Decision] severity: 'warning' 的校验规则没有受众概念 —— UI 级劝导在 seed/bootstrap 等机器写入路径上照样求值并打爆启动日志,且按写入次数而非按行计数 #13889

Description

@huangyiirene

按维护者指示上行立卡(2026-08-31,hotcrm#1203 决裁,逐字:「#1203 种子数据触发 related_to_required 警告,这种也是平台的问题啊」)。

实测事实(hotcrm#1203 + PR #1291 全记录,dev 全枚举 + PM 独立复核 + 消融验证)

  • hotcrm 的 related_to_required(severity: 'warning',文案 "At least one related record should be selected")是写给表单中的人的劝导。种子里恰好一行合法的内部任务(刻意不挂父记录、注释在场)触发它;
  • 平台对 warning 级规则没有受众/通道概念:同一条规则在 seed 加载、demo_bootstrap 认领回写等机器写入路径上照样求值,以 WARN 打进服务器启动日志 —— 一条给人看的表单提示变成了「启动诊断」,读起来像 boot 失败;
  • 按写入计数,不按行(hotcrm#1203 dev 发现 3):bootstrap 认领扫描回写 owner_id 会对业务字段未变的行重新求值全部规则 ⇒ 同一行清库首启响两次;任何数启动日志 warning 的人都会按「被认领对象数」高估;
  • 应用侧无路可走而不弯东西:达成「零警告」只能强行挂父(弯数据)或删规则(削门禁)—— hotcrm#1203 的四选项分析已排除两者,而「容忍日志行」把成本留给每一个全新部署的第一印象。

要裁的形状(平台统一,⛔ 应用不逐行辩护)

  • A · 声明受众:ValidationRuleSchema 给 advisory 级规则一个受众/通道位(如 audience: 'interactive' 或按 severity 约定)—— warning 级只在用户交互写入时求值/呈现,机器路径(seed/bootstrap/system)跳过或聚合。条款②面(spec 契约扩宽或语义收窄),需设计混用与默认值;
  • B · 引导路径聚合:seed/bootstrap 加载器把 advisory 命中聚合为一行摘要(「N 行触发 advisory 规则 X,详见…」),不改规则语义,只改日志形状;同笔修「按写入计数」(业务字段未变的系统回写不重复求值 advisory);
  • C · 维持并文档化(warning 即全路径日志,应用自行容忍)—— hotcrm#1203 的经验说明这会把每个样板/应用逼进「弯数据 vs 忍噪音」的假选择。

相关但不并卡

Refs: hotcrm#1203(测量全记录,现象卡已收口)· hotcrm PR #1291(防回归钉)· 2026-08-31 应用仓三原则 objectstack#13848。

Activity

  1. added
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    domain:specDomain lane — seat card indexed by label:pm:seat
    on Aug 31, 2026
  2. huangyiirene commented on Sep 1, 2026

    @huangyiirene
    CollaboratorAuthor

    裁决:B —— 引导路径聚合 + 修「按写入计数」;A 挂绊线停放,C 排除(维护者 2026-09-01,总监批 #23)

    项目总监席 · session session_01KGtaLpkW1mycWgkbSb3H6t · 维护者对本批逐字:「同意」。

    1. B:seed / bootstrap 加载路径把 advisory(warning 级)命中聚合为一行摘要(「N 行触发 advisory 规则 X,详见…」),⛔ 不改规则语义,只改日志形状;
    2. 同笔修计数缺陷:业务字段未变的系统回写(如 bootstrap 认领扫描写 owner_id)不重复求值 advisory 规则 —— 同一行清库首启只响一次,按行不按写入;
    3. A(ValidationRuleSchema 加受众/通道位)挂绊线停放:实测伤害全在引导路径的日志形状与重复求值,不在「缺一个可编写键」;⛔ 不为尚无第二例的需求铸造可编写面。绊线:若出现「某 warning 规则就该在机器路径响」的实例 ⇒ 受众键成真需求,census 随卡回决策箱;
    4. C 排除(把「弯数据 vs 忍噪音」的假选择留给每个应用,hotcrm#1203 已证)。

    再锚定:B 的落点在加载器侧(seed loader + bootstrap 认领回写的求值门),不触 spec 声明面 ⇒ domain:spec → domain:engine,同笔改挂。相关不并卡维持:种子日期 CEL 信封(工具面)、双 autonumber(#8686)。

    状态转移(同笔)

    needs-user-decision → pm:queue;改挂 domain:engine。验收锚 = hotcrm#1203 的实测记录(同一行首启只响一次、启动日志一行摘要)。


    Generated by Claude Code

  3. added
    pm:queueReady for the PM dispatch loop
    domain:engineRetired 2026-10-22, alias of domain:server (engine surface); in-flight cards keep it until they land
    and removed
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    domain:specDomain lane — seat card indexed by label:pm:seat
    on Sep 1, 2026
  4. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Sep 4, 2026
  5. self-assigned this
    on Sep 4, 2026
  6. zhuangjianguo commented on Sep 4, 2026

    @zhuangjianguo
    Collaborator

    Claim: PM loop round R17
    Session: session_01ARYe3yQTQCUFm5qPYNgKaJ
    Branch: claude/issue-13889-advisory-boot-path-aggregation
    Worktree: objectstack-issue-13889
    Domain: domain:engine
    File surface: to be established by the dev's own locating measurement — the ruling's named sites do not resolve in this repo (see below). Expected around packages/objectql/src/validation/** and/or the seed-loading path. ⛔ packages/objectql/src/engine.ts is a stop-and-report boundary, not a place to start editing (stop on breach; explain in the report).
    Container & model: M, mode:subagent, model: opus — the ruling fixes the direction, but "which write is a system write-back whose business fields did not change" is a judgment the implementation has to define and defend. ⛔ Not sonnet. ⛔ Not fable: the tier is quota-exhausted (measured twice this fire, HTTP 429) and this card does not need it — see Clause-② below.
    Clause-②: no — and this is the ruling's own words, not my reading: 「⛔ 不改规则语义,只改日志形状」 and 「不触 spec 声明面」. The accept/reject set does not move and no published surface widens. ⚠️ If the implementation finds it cannot do B without touching packages/spec, that is a fork to report, not a scope to take — option A (a ValidationRuleSchema audience key) was explicitly parked, so drifting into it would execute a direction the maintainer declined.
    Serial constraints cleared: read 2026-09-04T08:44Z. engine.ts is free but reserved — #14666 is ruled, ready, and blocked only on the exhausted tier; I am holding the slot for it rather than letting a card with an unestablished landing site take it by accident. No open PR of this seat touches packages/objectql/src/validation/** or the seed path. ⚠️ #15195 (ADR-0131 C1) will rewrite the seed loader's stamping when it unblocks, but it is pm:blocked on #15193 and not in flight, so there is no live collision — flagging it because the two will meet later.


    Decision re-read — ruled B, and the ruling is unusually specific

    Maintainer via the director seat (批 #23, 2026-09-01), verbatim 「同意」, adopting B:

    1. B — the seed / bootstrap load path aggregates advisory (warning-level) hits into one summary line (「N 行触发 advisory 规则 X,详见…」). 「⛔ 不改规则语义,只改日志形状」.
    2. Same stroke, fix the counting defect — a system write-back whose business fields did not change (the ruling's example: a bootstrap claim scan writing owner_id) must not re-evaluate advisory rules. 「同一行清库首启只响一次,按行不按写入」.
    3. ⛔ A is parked behind a tripwire — no audience/channel key on ValidationRuleSchema: 「⛔ 不为尚无第二例的需求铸造可编写面」. The tripwire is a second instance where a warning rule genuinely should fire on a machine path.
    4. ⛔ C excluded — leaving each app the false choice between bending data and tolerating noise.

    Acceptance anchor: hotcrm#1203's measured record — one row rings once on a clean-database first boot, and the startup log carries one summary line.

    ⚠️ Zone 2 — the ruling's named landing sites DO NOT RESOLVE in this repo. Measured, with controls.

    On origin/main @ c0d32cdac, 2026-09-04T08:44Z:

    grep result
    demo_bootstrap — whole repo, not just packages/ 0
    CONTROL — bootstrap anywhere under packages/ fires (cli/src/commands/meta/resync.ts and others) ⇒ the grep reads files
    owner_id writes tagged claim/bootstrap/backfill under metadata-protocol/src/ 0
    advisory / advisories in seed-loader.ts 0
    severity?: 'error' | 'warning' | 'info' present — packages/objectql/src/validation/rule-validator.ts:232

    ⇒ demo_bootstrap and the claim-scan write-back are hotcrm-side, which fits: the ruling's acceptance anchor is hotcrm#1203, and the card was raised upward from that repo. The platform half must therefore be located by behaviour rather than by the ruling's names:

    • where advisory hits are emitted on the seed/boot path (the thing to aggregate), and
    • where the write path decides to evaluate validations at all (the thing that must stop re-evaluating when business fields are unchanged).

    ⭐ This is the dev's first deliverable and first chance to falsify me: locate both, by symbol, and state them before changing anything. If the honest reading is that one half lives only in hotcrm, say so — that is a complete outcome and a cross-repo card, ⛔ not something to approximate from this lane.

    ⚠️ Stop-and-report if the landing site is packages/objectql/src/engine.ts. It is this lane's single serial resource and I am holding it for #14666. ⛔ Do not start editing it; report the measurement and stop.

    Related but ⛔ not this card

    Seed dates are unparsed CEL envelopes (evaluating date predicates offline against seeds yields confidently wrong answers) — a separate tooling concern. Double autonumber counter warnings are #8686.


    Generated by Claude Code

  7. zhuangjianguo commented on Sep 4, 2026

    @zhuangjianguo
    Collaborator

    Claim (dev seat, os-dev subagent)

    Session: session_01ARYe3yQTQCUFm5qPYNgKaJ
    Branch: claude/issue-13889-advisory-boot-path-aggregation
    Worktree: objectstack-issue-13889

    Working the ruling in comment 5494461940 (option B): aggregate advisory hits on the seed/boot load path into one summary line, and in the same stroke stop re-evaluating advisory rules on a system write-back whose business fields did not change. ⛔ Not touching the assignee field; ⛔ not drifting into parked option A (ValidationRuleSchema audience key).

    First deliverable is the locating measurement: the ruling's named sites (demo_bootstrap, the claim-scan owner_id write-back) do not resolve in this repo, so both halves have to be located by behaviour. If a half turns out to live only in hotcrm, that is what the report will say.


    Generated by Claude Code

  8. added 4 commits that reference this issue on Sep 4, 2026
  9. zhuangjianguo commented on Sep 4, 2026

    @zhuangjianguo
    Collaborator

    os-dev-report

    {
      "issue": 13889,
      "status": "done",
      "branch": "claude/issue-13889-advisory-boot-path-aggregation",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/15306",
      "premise_still_valid": true,
      "summary": "The premise holds, but the ruling's NAMES do not: `demo_bootstrap` is 0 hits repo-wide (control: `bootstrap` under packages/ hits 533 files), so both platform halves were located by behaviour. (a) Advisory hits are emitted in `packages/objectql/src/validation/rule-validator.ts` — the `logger.warn` inside `evaluateValidationRules`, one line per row per violated rule — NOT in the seed loader, which never mentions advisories (0 hits; control `seedReplay` hits that same file). (b) The claim-scan write-back is `packages/plugins/plugin-security/src/claim-seed-ownership.ts:342`: `ql.update(name, { owner_id: adminUserId }, { where: predicate, multi: true, context: { isSystem: true } })`. `packages/objectql/src/engine.ts` was NOT edited — I confirmed every `evaluateValidationRules` call site lives there, which is exactly why neither half was built by threading a parameter from it; instead half 1 uses an ambient scope in `@objectstack/core` (the layer both ends already depend on; objectql depends on metadata-protocol, never the reverse) and half 2 is a pure predicate inside the validator. `packages/spec` is untouched, so parked option A was not drifted into. Half 1: `SeedLoaderService.load()` runs inside an AsyncLocalStorage advisory-aggregation scope and reports one summary line per rule (rule, object, row count, the rule's own message, example rows) instead of N per-row lines; ALS rather than a module flag because a per-org seed replay runs on a LIVE server and a global would swallow concurrent interactive writes' advisories. Half 2: an `update` whose payload touches only platform-injected system columns re-evaluates no advisory rules — membership resolved per object by the declared authority `resolveInjectedSystemColumns`, never a literal list — so a seeded row rings once, not once per write. `error`-severity rules are untouched by both halves. THIRD MEASUREMENT asked for: 'business fields did not change' was NOT already available mechanically — the engine distinguishes `seedReplay`/`skipStateMachine`/`isSystem`, all of which say who issued a write, none of which say what it moved; the predicate had to be built, and its cost is one Set lookup per payload key. Stated blind spots, in code: soft delete (`is_deleted`/`deleted_at` are not injected columns, so those still evaluate — deliberate, retiring a row is a business event); a business field rewritten to its existing value (needs `previous`, absent on the bulk seam, and would change what an interactive re-PUT reports); an author-declared field literally named `owner_id`. Also stated rather than filed: app-plugin's metadata-service FALLBACK seeding path writes rows via `ql.insert` outside SeedLoaderService, so it keeps per-row lines — it runs only when the boot is already degraded, and it is documented at the scope. NO TRIPWIRE FOUND: I turned up no case where a warning rule genuinely should fire on a machine path, so option A stays parked with no new evidence against it. One correction to my own measurement: I first reported objectql/src as never importing metadata-protocol; that zero was a bad `**` pathspec — it actually imports it in 6 non-test files, which makes the dependency direction I relied on even better established.",
      "tests": "All 47 gate families derived by `node scripts/pm/dispatch-gates.mjs` (NO path arguments, `--repo objectstack-ai/objectstack` asserted) are GREEN; ratchet families + pins re-run on final commit 28ea75b4a after the last `origin/main` merge (merge-base verified to ADVANCE twice: 0e24b0c2c -> fbecffe1d -> 5b09356b7). PER FAMILY: 45 of 47 green on first run. 2 red, both real, both repaired: (1) `check:objectql-double-limit` — my new test's `find` double was limit-blind; fixed by applying the caller's bound after the filter by presence (it matters: `claimSeedOwnership`'s paged fallback reads with `limit: CLAIM_PAGE_ROWS`); re-run green. (2) `check-system-context-census` — pure line rot, my 61-line insertion in seed-loader.ts moved 3 anchors cited by `content/docs/permissions/system-context.mdx`; repaired with the gate's own `--fix` (only line numbers moved); re-run green. 2 more reported exit 3 = PREREQUISITE NOT MET (`check:dual-build-cjs-loads`, `check:type-check-debt`) — read as NOT MEASURED, never as passed; I built the closure (`turbo run build --filter=./packages/* --filter=./packages/*/*`, 71 tasks, 5m24s) and re-ran both: green (`--re-measure`: 15 ledger entries, 204 raw tsc errors, none above its recorded number). SUITES: @objectstack/core 48 files/1159 tests, @objectstack/metadata-protocol 161 files (2 skipped)/2370 tests (10 skipped), @objectstack/objectql 270 files/4631 tests — all pass. Typecheck green for all three; their `check:test-typecheck` compiles the test layer, so the new pins are genuinely type-checked rather than excluded. NEW PINS (5, all green): `packages/objectql/src/validation/advisory-boot-path.test.ts`, driven through a REAL ObjectQL engine, the REAL SeedLoaderService and the real claim-scan write shape. ABLATIONS — both legs rebuilt where resolution requires it, mutation confirmed ON DISK by anchored occurrence counts (injected marker + deleted-text counts), never a bare --stat, with an absolute-path `trap ... EXIT INT TERM` restore: (A) aggregation removed from @objectstack/core — FIRST ATTEMPT WAS VOID and I re-ran it: the preflight caught that my marker sat in a COMMENT the build strips, so it reached only sourcemaps; re-done with a `String('ABLATION_13889_A')` marker, `ablation-dist-preflight` then confirmed it live in dist/index.js AND dist/index.cjs (the suite resolves @objectstack/core through dist — objectql declares no vitest alias), and PIN 1 + PIN 2 went RED; restore leg rebuilt and preflight `--absent` confirmed the marker gone from all 12 built files, whole-tree `git status --porcelain` clean, worktree hash == HEAD blob hash. (B) by-row gate deleted from rule-validator.ts (src-resolved, no dist between edit and verdict): PIN 1 RED with the defect's exact shape — `expected [ ...(2) ] to have a length of 1 but got 2`, i.e. the row ringing TWICE, which is hotcrm#1203's measurement reproduced — and PIN 3b red too; restore verified by hash equality and a clean whole tree. ZERO-HIT GREPS, each with a FIRING control, measured at merge base fbecffe1d: `demo_bootstrap` repo-wide 0 (control `bootstrap` under packages/ = 533 files); `advisor*` in seed-loader.ts 0 (control `seedReplay` in that same file = 1); `owner_id:` writes under metadata-protocol/src 0 (control `owner_id` mentioned there = 5 files). One control did NOT fire on first run (objectql/src importing metadata-protocol) — I treated that zero as UNPROVEN and re-measured with a working pathspec: it is 81 files, 6 of them non-test. Control-byte scan clean over every file I touched (`grep -naP` for the C0 range); note a raw NUL was materialized into a new file from an escape I typed and was found and removed before any commit.",
      "mcp_calls": "7 — 2 issue reads, 2 issue comments (claim + this report), 1 create_pull_request, 1 pull_request_read (full body read-back), plus this comment's read-back",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  10. added
    needs:contract-reviewMarker only — PR awaits its at-tier Contract review PASS; no check reads it, the record decides
    on Sep 4, 2026
  11. added a commit that references this issue on Sep 4, 2026
  12. zhuangjianguo commented on Sep 4, 2026

    @zhuangjianguo
    Collaborator

    os-dev-report

    Supersedes comment 5538594231 (docs-accuracy round; 47 gate families became 75, 7 changed files became 9).

    {
      "issue": 13889,
      "status": "done",
      "branch": "claude/issue-13889-advisory-boot-path-aggregation",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/15306",
      "premise_still_valid": true,
      "summary": "Unchanged from the first report for the code halves; this round is the docs-accuracy repair. THREE sentences my change falsified are corrected, all verified on the tree I actually edit (HEAD 834991e11, merge-base ed217e697) rather than on the drift bot's tree 18156d74a or the read tree 5b09356b7 — line numbers as found: `content/docs/protocol/objectql/state-machine.mdx:111` ('warning/info are logged' — now states logged per write off the machine path, folded into one summary line per rule on a seed/boot load, and not evaluated at all on a system-column-only update); `content/docs/data-modeling/validation.mdx:86` (severity table row 'Shows a warning but allows save' — advisory hits are never returned to the caller, only logged); `validation.mdx:114-115` ('an unevaluable warning/info rule is logged and does not throw' — qualified to the writes where advisory rules are evaluated at all, since half 2 drops them before evaluability is reached). Net +6/-3 across the two pages, confined to rule semantics; neither page restates the aggregation design. YOUR READING OF THE THIRD PAIR IS CORRECT and I checked rather than assumed: `state-machine.mdx:112` and `validation.mdx:209` ('every OTHER validation still runs' of seed writes) SURVIVE and were not edited — advisory rules still evaluate on the seed INSERT (only reporting moved), and the by-row gate is guarded `mode === 'update'` so it cannot reach a seed insert at all; what those sentences promise a seed must still satisfy is rejection, which is error-severity and untouched by both halves. ⛔ `content/docs/releases/v16.mdx` and `v17.mdx` NOT touched — release-owned; I did not evaluate whether they are wrong, so no separate card is requested on that ground. NOTED, no action asked: the bot's 'packages/core/src/index.ts yielded no anchor' means pages documenting the new export are NOT COVERED by that run — consistent with Clause-② being yes on my diff (six new public symbols on @objectstack/core: AdvisoryHit, AdvisoryGroup, ADVISORY_SAMPLE_ROWS, recordAdvisoryHit, isAggregatingAdvisories, runWithAdvisoryAggregation); `needs:contract-review` is on the PR and I have left it there. PR is still DRAFT, not flipped ready, not enqueued, auto-merge not armed.",
      "tests": "ALL 75 derived gate families GREEN, 0 red — re-derived with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` (NO path arguments) after the docs edits widened the change set from 6 to 8 paths; the 28 newly-implicated families are the docs ones (check:doc-anchors, check:doc-authoring, check:docs-single-h1, check:docs-redirects, check:docs-audit-scope, check:docs-section-name, check:doc-frontmatter, check:doc-route-spelling, check:section-landing-index, spec check:docs, spec check:skill-examples, spec check:variant-docs, spec check:yaml-examples, spec check:empty-state, spec check:liveness, spec check:strictness-ledger, lint check:doc-formula-expressions, lint check:doc-security-posture, check:corpus-claim-drift, check:role-word, check:published-readme-links, check:react-page-adapter-contract, check:skill-identifier-liveness, check:vendor-version-stamps, check:merge-driver and their self-tests). Per-family exit codes recorded to a results file: 75 rows, 0 non-zero. Exit codes captured by redirect-first (`cmd > log 2>&1; code=$?`), never through a pipe. The two families that returned exit 3 = PREREQUISITE NOT MET last round (`check:dual-build-cjs-loads`, `check:type-check-debt`) are green here on a REBUILT closure — the worktree removal after the first round took every `dist/` with it, so I rebuilt (`turbo run build --filter=./packages/* --filter=./packages/*/*`, exit 0, 3m13s) before running them rather than reading their refusal as a pass. Pins re-run on the moved tree: 5/5 green. Control-byte scan clean on both edited .mdx files. `git diff --name-only merge-base..HEAD -- content/docs/releases/` = 0 files, i.e. the release-owned guardrail is mechanically confirmed, not just asserted. Union ran on the FINAL commit 834991e11 with a clean `git status --porcelain`; local == remote confirmed after push.",
      "mcp_calls": "11 — 2 issue reads, 3 issue comments (claim + 2 reports), 1 create_pull_request, 2 pull_request_read, 1 PR comment, 1 comment read-back, 1 this comment",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  13. os-warren commented on Sep 4, 2026

    @os-warren
    Collaborator

    Contract review — PASS, one non-blocking observation recorded. Carriers come off both sides; PR #15306 goes ready with squash auto-merge armed.

    Director seat (项目总监席), session session_01LsEjuNMPitCHwEfYftZ1um, GitHub os-warren. Reviewed at head 834991e11 (merge base ed217e697), all nine files read locally from the fetched head.

    Implemented-by: os-dev subagent, session session_01ARYe3yQTQCUFm5qPYNgKaJ, claude-opus-5 (claim 5537948162)
    Reviewed-by: director seat, session session_01LsEjuNMPitCHwEfYftZ1um, claude-fable-5-1 — get_session fuse read at 11:33Z, session_context.model and last_served_model both claude-fable-5-1 = CONTRACT_REVIEW_TIER

    Against ruling B (5494461940)

    term reading on 834991e11
    aggregate advisory hits on the seed/boot path into one summary line; ⛔ log shape only ✅ SeedLoaderService.load() wraps loadDatasets in runWithAdvisoryAggregation; reportAdvisorySummary emits one warn per (object, rule) group with row count, the rule's own message and sample rows. evaluateValidationRules evaluates exactly as before — only the report channel moves while a scope is active; off-scope the per-write line is emitted verbatim (PIN 3a)
    a system write-back with unchanged business fields does not re-evaluate advisory rules; one row rings once per clean first boot ✅ writesOnlyInjectedSystemColumns: mode === 'update', non-empty payload, every key in resolveInjectedSystemColumns(objectSchema).names; advisory rules are dropped from the run, error rules untouched (PIN 3b/3c). PIN 1 drives the real claim-scan write shape through a real engine and goes red under ablation B with the defect's exact shape (2 rings → 1)
    A parked, no spec touch ✅ packages/spec untouched; the tripwire (a warning rule that genuinely should fire on a machine path) is on the record in the engine seat's 5538618737
    engine.ts reserved for #14666 ✅ untouched

    Clause-②: yes — six new exports from @objectstack/core (ADVISORY_SAMPLE_ROWS, AdvisoryHit, AdvisoryGroup, recordAdvisoryHit, isAggregatingAdvisories, runWithAdvisoryAggregation). The engine seat's re-derivation on the actual diff was correct, and correcting its own claim-time no was the right stroke. The seam's placement is sound: core is the floor both objectql and metadata-protocol stand on and the dependency runs one way; AsyncLocalStorage rather than a module flag because per-org seed replay runs on a live server; report in finally, never allowed to replace the caller's outcome.

    Runtime-shape check: node:async_hooks is a Node built-in imported at the top of a file re-exported from core's . entry. Core already imports node:crypto / node:path at top level in six index-reachable files and declares no browser field, so this adds no new runtime class; check:dual-build-cjs-loads re-measured 0 on the built closure.

    Changeset: core / objectql / metadata-protocol minor — additive public surface on core, behaviour change on a shipped boot path in the other two; consistent with the tree's convention. The docs round corrects three sentences the change falsified; content/docs/releases/ untouched (measured, 0 files).

    Non-blocking observation, for the record only: AdvisoryHit.severity and AdvisoryGroup.severity are typed string while the docblock says never 'error'. The only producer is rule-validator.ts, which passes the rule's 'warning' | 'info'; if a second consumer ever appears, tightening is a one-line narrowing on a seam with one producer. No patch round, no card.

    CI on 834991e11: green across the board (Lint & Repo Gates, Type Check workspace / debt / source / consumer, Test Core 1–6, Dogfood, Spec property liveness); mergeable_state: clean.

    Landing (landing-operations B)


    Generated by Claude Code

  14. removed
    needs:contract-reviewMarker only — PR awaits its at-tier Contract review PASS; no check reads it, the record decides
    on Sep 4, 2026
  15. removed their assignment
    on Sep 4, 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:engineRetired 2026-10-22, alias of domain:server (engine surface); in-flight cards keep it until they land

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions