From 65564d3ff6e2d34908e216c40fa92f07c5e890c0 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 11:48:49 +0000 Subject: [PATCH 1/2] docs(adr): retire ADR-0076 D11's contention metric and the reopen threshold it fed (#10076) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit D11's 2026-08-16 revision installed a replacement metric — the per-PM-round count of dispatchable cards blocked from running concurrently because both land in `packages/rest/src/rest-server.ts` — and gated reopening option A on it sustaining above ~1/round. Nothing ever computed that counter. Batch independence is computed per round to schedule work and is not persisted; no script, gate, workflow or agent instruction file in this repo produces, stores or reads the series. Its reporting anchor (#5949) has been closed/completed since 2026-08-16, so every reading owed after that date had nowhere live to land, and the readings that were filed went into a closed card. So the ADR claimed a reopen threshold that nothing fed. Per the maintainer ruling of 2026-08-20 (option B), the metric and the threshold are retired together, in writing: - section ③'s metric and threshold are marked retired IN PLACE rather than deleted, since the 2026-08-16 decision was argued from them; - a new 2026-08-20 revision records what is retired, why, and — explicitly — what it does not do: it does not revive the raw `wc -l` series, does not make B permanent, and does not touch ①/④ or the 2026-08-15 ruling; - the status line and the D11 status bullet stop pointing readers at a live metric. Reopening A is now an ordinary new decision under Prime Directive #13, argued from a measurement taken for that purpose, rather than an automatic trigger nothing was ever going to pull. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_019bmVFqoQPq63zhKrxdYG1r --- docs/adr/0076-objectql-core-tiering.md | 31 ++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/docs/adr/0076-objectql-core-tiering.md b/docs/adr/0076-objectql-core-tiering.md index af6f734e9f..f62e5dc452 100644 --- a/docs/adr/0076-objectql-core-tiering.md +++ b/docs/adr/0076-objectql-core-tiering.md @@ -1,6 +1,6 @@ # ADR-0076: objectql is the data engine — relocate metadata management (protocol) out of it; enforce the boundary; defer the engine repo-split -**Status**: Accepted — D1/D2/D4/D5/D6/D8/D9 implemented; D10/D12 partially landed; **D11 implemented on the dispatcher half and *decided* on the rest-server half — delineated, not split** (maintainer ruling 2026-08-15; revision recorded under D11 by #9024); D3 unbuilt; D7 deliberately deferred (proposed 2026-06-28, rev. 9 · calibrated 2026-08-05 by #5063 · D11 revised 2026-08-16). This ADR has been binding for some time — `AGENTS.md`, `packages/runtime/src/domains/*` (D11's own output), `packages/objectql`'s core boundary and ADR-0121 all cite it as a settled decision — while the header still read `Proposed`; per Prime Directive #13 an accepted decision binds until superseded, so the header is corrected here and, following #4919's calibration of ADR-0078, **per decision** rather than by a single flip. Verified against source (not against the rev.9 narrative), 2026-08-05: +**Status**: Accepted — D1/D2/D4/D5/D6/D8/D9 implemented; D10/D12 partially landed; **D11 implemented on the dispatcher half and *decided* on the rest-server half — delineated, not split** (maintainer ruling 2026-08-15; revision recorded under D11 by #9024); D3 unbuilt; D7 deliberately deferred (proposed 2026-06-28, rev. 9 · calibrated 2026-08-05 by #5063 · D11 revised 2026-08-16 · D11's replacement metric and reopen threshold retired 2026-08-20 by #10076). This ADR has been binding for some time — `AGENTS.md`, `packages/runtime/src/domains/*` (D11's own output), `packages/objectql`'s core boundary and ADR-0121 all cite it as a settled decision — while the header still read `Proposed`; per Prime Directive #13 an accepted decision binds until superseded, so the header is corrected here and, following #4919's calibration of ADR-0078, **per decision** rather than by a single flip. Verified against source (not against the rev.9 narrative), 2026-08-05: - **D1 — implemented, with OQ#2 unexecuted.** `packages/metadata-protocol/` owns `protocol.ts`, `sys-metadata-repository.ts` and `metadata-diagnostics.ts`; `packages/objectql` depends on it one-way (`packages/objectql/package.json`, `src/plugin.ts` imports `assembleMetadataProtocol`). **But `SysMetadataEngine` never moved to `@objectstack/metadata-core`** — it is still declared in `packages/metadata-protocol/src/sys-metadata-repository.ts` and `packages/metadata-core/src/` has no occurrence of the name. OQ#2 is therefore *unexecuted*, not resolved; the D1 bullet below states the intent, not the state. - **D2 — implemented.** `packages/objectql/src/core-boundary.ratchet.test.ts` walks the `core.ts` import closure and forbids `@objectstack/metadata-protocol` plus the local `plugin` / `kernel-factory` modules. @@ -8,7 +8,7 @@ - **D4/D5/D6/D8 — ratifications, all still true.** `@objectstack/formula` remains an `objectql` dependency used by `engine.ts` / `hook-wrappers.ts` / `validation/rule-validator.ts` (D4). `@objectstack/spec` carries only `zod` as a runtime dependency, and `examples/embed-objectql/` boots `@objectstack/objectql/core` against an ordinary `ObjectSchema.create` object — the same definition shape a full backend ships (D5, and Phasing P4). `engine.ts`'s `registerMiddleware` is the attachment point `plugin-security` and `plugin-sharing` actually use (D6), and the engine still hard-codes no governance (D8) — the D2 ratchet is what keeps it that way. - **D9 — implemented, past step-1, at its own rev.7 end-state.** `packages/spec/src/api/protocol.zod.ts` declares ten per-domain contracts (`DataProtocol`, `MetadataProtocol`, `AnalyticsProtocol`, `AutomationProtocol`, `PackageProtocol`, `ViewProtocol`, `PermissionProtocol`, `RealtimeProtocol`, `NotificationProtocol`, `I18nProtocol`), and **no `ObjectStackProtocol` union type is declared or exported anywhere in the repo** — the transitional composed alias is *dissolved*, not merely deprecated. Consumers are narrowing accordingly: `packages/rest/src/rest-server.ts` types its dependency as `RestProtocol = DataProtocol & MetadataProtocol`. - **D10 — partially implemented.** Landed: the feed domain and then the analytics fallback left the facade (analytics now has exactly one implementation, in `service-analytics`), and OQ#7 resolved to keep the package name. Outstanding: the central facade class this decision rules out **still exists** — `packages/metadata-protocol/src/protocol.ts` declares `class ObjectStackProtocolImplementation implements DataProtocol, MetadataProtocol, PackageProtocol` — so the `DataProtocol` implementation has not moved to the engine-adjacent/transport layer and **OQ#6 remains open**. -- **D11 — dispatcher half implemented; rest-server half *decided*: delineated, not split.** The dispatcher decomposed exactly as OQ#9's verdict describes: every domain body lives in `packages/runtime/src/domains/*`, routing goes through `packages/runtime/src/domain-handler-registry.ts`'s `DomainHandlerRegistry`, and `http-dispatcher.ts` is down to ~2.1k LOC from the ~3.8k recorded below (2,087 lines measured at `dc280d326`; this bullet recorded ~1.7k at the 2026-08-05 calibration). Multi-adapter is validated (OQ#10) by `packages/qa/http-conformance`. The second half — `packages/rest/src/rest-server.ts` — is **not** decomposed and, by the maintainer ruling of 2026-08-15, **will not be**: it is already delineated by domain inside the file, and the decomposition programme does not open. See the **2026-08-16 revision at the end of D11** for the structural reading, the price that decision knowingly accepts, and the metric that replaces the retired line count. *(Until 2026-08-16 this bullet read "the rest-server half is untouched" and compared two raw line counts — i.e. it recorded an open obligation, and measured it with the proxy the ruling retired. Both are corrected: the obligation is a closed decision, and the raw-LOC comparison is gone rather than refreshed.)* +- **D11 — dispatcher half implemented; rest-server half *decided*: delineated, not split.** The dispatcher decomposed exactly as OQ#9's verdict describes: every domain body lives in `packages/runtime/src/domains/*`, routing goes through `packages/runtime/src/domain-handler-registry.ts`'s `DomainHandlerRegistry`, and `http-dispatcher.ts` is down to ~2.1k LOC from the ~3.8k recorded below (2,087 lines measured at `dc280d326`; this bullet recorded ~1.7k at the 2026-08-05 calibration). Multi-adapter is validated (OQ#10) by `packages/qa/http-conformance`. The second half — `packages/rest/src/rest-server.ts` — is **not** decomposed and, by the maintainer ruling of 2026-08-15, **will not be**: it is already delineated by domain inside the file, and the decomposition programme does not open. See the **2026-08-16 revision at the end of D11** for the structural reading and the price that decision knowingly accepts. The metric that replaced the retired line count is **itself retired** by the **2026-08-20 revision**, together with the `~1/round` reopen threshold it fed — nothing ever computed either, so there is now no standing metric on this file and no automatic reopen trigger. *(Until 2026-08-16 this bullet read "the rest-server half is untouched" and compared two raw line counts — i.e. it recorded an open obligation, and measured it with the proxy the ruling retired. Both are corrected: the obligation is a closed decision, and the raw-LOC comparison is gone rather than refreshed.)* - **D12 — framework side implemented; console side cross-repo and unverified from here.** `packages/spec/src/api/discovery.zod.ts` carries the `unavailable` / `degraded` / `stub` statuses plus `ServiceSelfInfoSchema` + `readServiceSelfInfo`; `svcAvailable` in `packages/runtime/src/http-dispatcher.ts` derives `status`/`handlerReady` from that marker for every slot (no hardcodes left); `packages/runtime/src/service-serveable.ts`'s `isServiceServeable` is the single predicate the dispatcher-owned domains, the route-mount gate and discovery all read. The consumer half ("trust only `handlerReady: true`") lives in cloud/objectui and cannot be confirmed from this repo. - **D7 — deferred as designed, and now instrumented.** `packages/objectql` is still in the monorepo. New since rev.9: `scripts/check-engine-split-ratio.mjs` and `.github/workflows/engine-split-metric.yml` compute and report the cross-package commit ratio that gates the split; it is deliberately **report-only** because the threshold itself is still OQ#5. @@ -153,13 +153,15 @@ D11 names two central transport layers. The dispatcher half was decomposed (OQ#9 **② The price this decision knowingly accepts — B is defensible, and it is not free.** Merge conflicts, affected-subset CI, and the PM lane's batch-independence rule all operate on **file paths**. A delineation expressed as methods inside one file is invisible to all three. Two agents editing `registerApprovalsEndpoints` and `registerReportsEndpoints` — blocks that share no line — still collide, still serialise, and still dirty the same CI subset. **Adopting B accepts that permanently, until someone reverses this decision.** It is written into the ADR so the price arrives with the decision instead of being discovered by whoever pays it. -**③ The replacement metric, and the reopen condition.** +**③ The replacement metric, and the reopen condition. ⛔ Both RETIRED 2026-08-20 — see the revision at the end of D11.** The block is kept, not deleted, because the 2026-08-16 decision was argued from it and a later reader needs to see what was withdrawn rather than find a gap. The line-count series this question was argued over is **retired, not paused**. It used raw `wc -l` on a file whose code and prose are close to the same size: measured with a comment-aware classifier at `dc280d326`, **5,665 code lines against 4,690 comment/blank (45.3%)** — and those comments are dense explanatory documentation (why a refusal is shaped as it is, why route order is load-bearing), not commented-out code. A raw line delta over such a file cannot separate "a new endpoint landed inline" from "someone documented a subtle refusal properly", so it cannot answer the question it was being used to answer. > **Replacement metric — per PM round: the number of times two dispatchable cards could not run concurrently because both land in `packages/rest/src/rest-server.ts`.** +> +> ⛔ **RETIRED 2026-08-20 (#10076).** Nothing ever computed this counter. Do not resume the series, and do not file readings into #5949 — that card has been closed since 2026-08-16. -It measures the cost this file demonstrably creates rather than its volume, and the dispatch loop already computes batch independence, so it needs no `git log` window — which is what repairs the 2026-08-08 hold's "measured merge-conflict tax" arm after shallow clones defeated two separate seats trying to measure it. **Reopen A if this counter sustains above ~1/round.** +It measures the cost this file demonstrably creates rather than its volume, and the dispatch loop already computes batch independence, so it needs no `git log` window — which is what repairs the 2026-08-08 hold's "measured merge-conflict tax" arm after shallow clones defeated two separate seats trying to measure it. ~~**Reopen A if this counter sustains above ~1/round.**~~ ⛔ **The threshold is withdrawn (2026-08-20).** It rested on a counter no mechanism produced, so the condition could not fire; reopening A is now an ordinary new decision under Prime Directive #13. See the revision below. - **First datapoint (2026-08-15 rounds, recorded with the ruling): 0.** Re-verified rather than restated: #8087, the one queued card that would land in this file, is still open and still blocked for a reason unrelated to it, so the collision never materialised. - **Re-derived for the 2026-08-16 round: 0.** None of the 24 open `pm:dispatched` cards lands in `rest-server.ts`. @@ -169,6 +171,27 @@ It measures the cost this file demonstrably creates rather than its volume, and **Which ruling figures moved.** The 18 methods and the domain axis are unchanged. The whole-file composition the ruling quoted — 6,008 code against 5,400 comment/blank, 11,408 total — was taken at `5047cb8ab` and is **superseded**: #8850's prologue extraction (landed as `8664a2c99`) moved 1,296 lines of ADR-0112 error/fault classification out to `error-response.ts` after that measurement. The same classifier used above reproduces `6,008 / 5,400` exactly at `5047cb8ab`, so the two readings differ because the file changed, not because the method did. `registerMetadataEndpointsInner` reads 2,324 lines on that tree against the ruling's ~2,328 (a method-boundary convention, not a disagreement) and 2,545 today, the `/meta` work since having landed inside it. Recorded as measured; ⛔ deliberately not averaged, rounded together, or reconciled in either direction. +#### Revision 2026-08-20 (#10076) — the per-round contention metric is RETIRED, and the reopen threshold goes with it [ruled] + +**Provenance.** Maintainer, decision-inbox session 2026-08-20, verbatim 「其他接受你的建议。」, accepting option **B** at [#10076](https://github.com/objectstack-ai/objectstack/issues/10076) — retire the contention-sequence reporting commitment, paired with this note. ⛔ Deliberately **not** a silent retirement: the condition of the ruling was that this record stop claiming a reopen threshold nothing feeds, in writing, with the reason attached. + +**What is retired.** ③'s replacement metric — the per-PM-round count of dispatchable cards that could not run concurrently because both land in `packages/rest/src/rest-server.ts` — **and the `~1/round` reopen condition that rested on it**. Neither is measured, reported nor consumed from this date. ⛔ Do not resume the series, and ⛔ do not file readings into #5949: posting measurements into a closed card is the precise shape this retirement exists to end. + +**Why — three findings, all re-measured at head for this revision.** + +1. **No mechanism ever computed it.** ③ reasoned that "the dispatch loop already computes batch independence, so it needs no `git log` window". Batch independence is computed per round to *schedule* work; it is not persisted, and the counter was never derived from it. Measured 2026-08-20: no script, gate, workflow or agent-instruction file in this repo produces, stores or reads the series. It lived in prose in one seat post, handed from seat to seat — which is a carrier, not a mechanism. +2. **Its anchor has been closed since 2026-08-16.** #5949 — the finding this whole revision line descends from — is `closed`/`completed` as of 2026-08-16T06:47:27Z. Every reading owed after that date had nowhere live to land, and the readings that were filed went into a closed card that no longer appears in any open-issue view. +3. **The delivery record shows the failure was structural, not personal.** Over rounds 15–28: round 23 filed a reading into the already-closed anchor and flagged it; rounds 24–25 produced no entry at all; rounds 26–28 were filed only retroactively, during the audit that produced this note. Three seats, thirteen rounds, one gap. ⭐ **An obligation with no live named reader and no trigger cannot fail loudly, so it reads as satisfied right up until somebody audits it** — the same defect class this repo has measured in `Restart-when:` hand-offs that delegate work to an unrelated card. + +**The threshold could not have carried a decision even if the series had been maintained.** Every reading from round 18 onward sat at or below `~1/round`, and the only two rounds ever re-checked (19 and 20) were **both** found mis-counted. So "it was once sustained above the threshold" is itself unverified. That weakens the case *for* reopening A and the case *against* it equally, and is recorded here rather than resolved in either direction. + +**⛔ What this revision does NOT do.** + +- It does **not** revive the raw `wc -l` line-count series. That was retired by the 2026-08-16 revision above on its own merits — a file that is ~45% explanatory prose cannot be judged by a line delta — and retiring its *replacement* does not hand the question back to it. There is now **no standing metric on this file**, deliberately. +- It does **not** make B permanent or unchallengeable. ②'s price stands unchanged: a delineation expressed as methods inside one file is invisible to merge conflicts, affected-subset CI and batch independence, and that is still the reason someone may want A. What changes is the route — reopening A is an ordinary new decision under Prime Directive #13, argued from a measurement taken **for that purpose**, rather than an automatic trigger nothing was ever going to pull. +- It does **not** touch ①'s structural reading, ④'s `registerMetadataEndpointsInner` carve-out (#8851), or the 2026-08-15 ruling recorded above that B rests on. + + ### D12 — Honest capabilities: discovery must distinguish real services from stubs/fallbacks [new — kernel review] **Root cause of agents being misled.** Several plugins register stub / dev / fallback services under canonical names, and the discovery builder reports *any* present service as fully real: `runtime/http-dispatcher.ts`'s `svcAvailable` hardcodes `{ enabled: true, status: 'available', handlerReady: true }` for every registered service — it **ignores stub markers** (its own comment even says "handlerReady:false … may be served by a stub", but the code never computes it). So `discovery.services.*` claims capabilities that are only stubbed, and consumers (AI agents, the console) trust them. A dev AI stub advertised this way has already confused an agent. From 2864eaa6fd3b7e3b861aae7e4b66a311a3d4ade7 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 11:52:29 +0000 Subject: [PATCH 2/2] docs(adr): mark the withdrawn D11 threshold in house style, not GFM strikethrough MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GFM matches strikethrough on a run of ONE or two tildes, and the withdrawn sentence contains a bare `~` in "~1/round" — so the marking depended on delimiter-run lengths rather than on being unambiguous. The rest of this record marks withdrawn text with a ⛔ prose marker and leaves the sentence standing (the "central route generator" framing above does exactly that), so this now does the same. No change to what is retired. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_019bmVFqoQPq63zhKrxdYG1r --- docs/adr/0076-objectql-core-tiering.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/adr/0076-objectql-core-tiering.md b/docs/adr/0076-objectql-core-tiering.md index f62e5dc452..bb3d390fa6 100644 --- a/docs/adr/0076-objectql-core-tiering.md +++ b/docs/adr/0076-objectql-core-tiering.md @@ -161,7 +161,7 @@ The line-count series this question was argued over is **retired, not paused**. > > ⛔ **RETIRED 2026-08-20 (#10076).** Nothing ever computed this counter. Do not resume the series, and do not file readings into #5949 — that card has been closed since 2026-08-16. -It measures the cost this file demonstrably creates rather than its volume, and the dispatch loop already computes batch independence, so it needs no `git log` window — which is what repairs the 2026-08-08 hold's "measured merge-conflict tax" arm after shallow clones defeated two separate seats trying to measure it. ~~**Reopen A if this counter sustains above ~1/round.**~~ ⛔ **The threshold is withdrawn (2026-08-20).** It rested on a counter no mechanism produced, so the condition could not fire; reopening A is now an ordinary new decision under Prime Directive #13. See the revision below. +It measures the cost this file demonstrably creates rather than its volume, and the dispatch loop already computes batch independence, so it needs no `git log` window — which is what repairs the 2026-08-08 hold's "measured merge-conflict tax" arm after shallow clones defeated two separate seats trying to measure it. **Reopen A if this counter sustains above ~1/round.** ⛔ **WITHDRAWN 2026-08-20 (#10076)** — the threshold rested on a counter no mechanism ever produced, so the condition could never fire. The sentence is left standing rather than struck because the 2026-08-16 decision was argued with it; reopening A is now an ordinary new decision under Prime Directive #13. See the revision below. - **First datapoint (2026-08-15 rounds, recorded with the ruling): 0.** Re-verified rather than restated: #8087, the one queued card that would land in this file, is still open and still blocked for a reason unrelated to it, so the collision never materialised. - **Re-derived for the 2026-08-16 round: 0.** None of the 24 open `pm:dispatched` cards lands in `rest-server.ts`.