Skip to content

Commit 529e535

Browse files
fix(metadata-protocol): a tenant's edit follows its item across the packages of one App (#31653)
<!-- claude-projects-attribution: {"github_login":"os-steve"} --> _Requested by **Steve** · [project thread](https://claude.ai/code/project/chan_01FdL1XeUyvVNxLreh3F4Qi9?thread=cmsg_01FdL1XeUyvVNxLreh3F4Qi9Wtro5Gqzz97Z6DD47Tkksr)_ Fixes #31593 Clause-②: no — no accept set changes; the lookup scope widens only within one suite (triage 6724363147) Metadata-protocol: none ## 维护者速读 - 改了什么:同一个 App 的某个条目(如视图)在新版本里换了所属包(扁平 → 组合拆包,或回滚),租户对它的修改现在会跟着条目走,在新的所属包下照常生效;按名读取、列表、分层读取三处一致。 - 为什么改:修改是按"编辑当时的所属包"存的,拆包或回滚后读取只找新所属包,修改还在库里却不再生效,客户看到定制"消失"。HotCRM 6.2.0 组合版上线前必须修。 - 风险与回滚:只改读取时的查找范围,不写任何数据,不改保存行为;"同一 App"只从安装记录(`appOf` 读已安装清单)判定,旧模块已卸载时以该行绑定在本 App 的 `.custom` 包为凭据,绝不按 id 前缀猜。回滚即还原本 PR。 - 先看:`packages/metadata-protocol/src/suite-moved-overlay.ts`(两条判定)、`packages/metadata-protocol/src/protocol-overlay-merge.ts`(`servedOverlayRowCandidates` 的候选顺序与 `holdsServedOverlayCandidate`)、`packages/metadata-protocol/src/protocol-environment-rows.ts`(按名读取接入)。 - 两分钟验证:`pnpm --filter @objectstack/runtime exec vitest run src/domains/meta-overlay-follows-suite.test.ts`,预期 6 个全绿(需先构建 metadata-protocol)。 - 需要你做的:无,席位落地。 ## What changes **Premise as measured on `origin/main`.** The card measured the edit as a row bound to the base (`package_id = app.objectstack.hotcrm`). On `main` that save is closed (ADR-0146 D11): a save naming the read-only base is refused and prescribes the App's pair, so today the edit lands in `app.objectstack.hotcrm.custom` declaring `overrides: { package: OWNER_AT_THE_TIME, basedOn }`. The pair is one per App (ADR-0146 D3 as amended), so after a suite split only the declaration still names the former owner, and the same symptom recurs: the row is kept, the reads serve the vendor's item. Both shapes are fixed here: the pair's declared override (every edit made on a current kernel) and the pre-close row bound to the base (every edit a tenant made on an earlier release, which `os migrate packageless-overlays` cannot move once the bound base stops shipping the name). **The rule (option 1, the seat's ruling): a tenant's edit follows its item across the packages of one App.** New module `packages/metadata-protocol/src/suite-moved-overlay.ts`, two pure predicates: - `isMovedOverrideRowOf(row, base, installed, ships)`: the body declares `overrides.package` = a former owner, not `base`; `base` ships the name and the former owner does not; the former owner is an installed package of `base`'s App, or is no longer installed. Every other check is `isServedOverrideRowOf`'s own, asked of the row as if it named `base` (binding is one of `base`'s pairs, overridable type), so the one predicate stays the one spelling of binding and type. - `isMovedBaseRowOf(row, base, installed, ships)`: the row is bound to another installed, non-pair package of `base`'s App that no longer ships the name while `base` does, of an overridable type, declaring nothing. **Same App, from the install record, never a prefix.** Two installed packages are of one App when `appOf` (metadata-core) answers the same App over the registry's installed manifests. When the former owner is no longer installed (rollback from composed to flat), the record is the row's binding to the App's pair: the save door admits a declaration into a pair only for a package of that pair's App (`isPairOf`, ADR-0146 D2 write gate). **Where it is read: the one served-row resolution, so every read agrees.** `servedOverlayRowCandidates` gains two candidates when the read names which packages ship the item (`ships`, new optional field of the address): the pair's moved override right after the package's own declared override, and a moved base row LAST, after the package-less row (which wins over another package's row, ADR-0146 D8 item 1). `holdsServedOverlayCandidate` is the one reading both readers apply. `findServedOverlayRow` (by-name and layered reads) reads `ships` from `shippedArtifactsOf`; `mergePackageAwareOverlay` (list) reads it from the slot's lower-layer contributions. A moved edit is served as the current owner's item (`overrideOf`, the current owner's envelope). Two former owners' base rows that could both follow are never guessed between. Write addresses pass no `ships` and are unchanged. **What it never does.** Writes nothing (the row keeps its binding and body; a rollback needs no reverse step). Never takes an item from a package that still ships it. Never reaches an `object` (still `ITEM_LOCKED`, and a stored object row never follows). Files: `suite-moved-overlay.ts` (new), `protocol-overlay-merge.ts`, `protocol-environment-rows.ts`, `protocol.ts` (two host members passed to `findServedOverlayRow`), the test, a patch changeset. No `packages/spec/**`, no `ui/**`, no `registry.ts`, no `served-override.ts` (#31627's file is not edited). No forced test edits. ## Tests New `packages/runtime/src/domains/meta-overlay-follows-suite.test.ts`, 6 cases. One SQLite file is the tenant DB; each boot is a fresh kernel over it with one version's packages (real `ObjectQL`, `SqlDriver`, registry, protocol; nothing stubbed). Each positive case reads by name with and without `?package=`, the list and the layered `effective`. 1. flat → composed, edit in the App's pair declaring the App: served at the module. 2. composed → flat (rollback), edit declaring the module: served at the App with the module uninstalled; re-upgrade serves it again. 3. flat → composed, a pre-close row bound to the App itself: served at the module. 4. NEGATIVE: a row bound to an unrelated installed App, that App's pair declaring HotCRM's App, and HotCRM's pair declaring the unrelated App: none applies to the module's view. 5. CONTROL: the former owner still ships the view: it keeps its edit, the module serves its own view. 6. CONTROL: an object save naming the owner answers `ITEM_LOCKED`; an object row bound to the App never follows to the module. <details><summary>Red on main first (6924440, metadata-protocol src unchanged): 3 failed | 3 passed (6)</summary> ``` × flat → composed: the edit saved under the App is served at the module that ships the view now × composed → flat (rollback): the edit saved under the module is served at the App, though the module is gone × flat → composed: an edit bound to the App itself (a Regime O row from before the D11 close) follows too - "bare": "Open cases (ours)", + "bare": "All", - "layered": "Open cases (ours)", + "layered": "All", "list": [["app.objectstack.hotcrm.service", - "Open cases (ours)" + "All"]], - "owner": "Open cases (ours)", + "owner": "All", Tests 3 failed | 3 passed (6) ``` The 3 passes are the NEGATIVE and the two CONTROLs, as on main. A first measurement also confirmed the premise shift: the Regime O save naming the base answers the D11 refusal prescribing `app.objectstack.hotcrm.custom`. </details> <details><summary>Ablations (fix committed first; `scripts/ablation-replace.mjs`, dist preflight, restore proven blob == HEAD each leg)</summary> The runtime test resolves `@objectstack/metadata-protocol` through its `dist/`, so each leg rebuilt the package and `ablation-dist-preflight.mjs` proved the marker present (2 built files) before the run and absent (all 24 files) after the restore build; the tree was clean against HEAD after each leg. - **A. Same-App check removed** (`ofOneApp` answers true for any installed pair, blob `05cc47b2` → `51420353`): `Tests 1 failed | 5 passed (6)`, the NEGATIVE case, `expected 'Open cases (ours)' to be 'All'` (the row bound to the unrelated App followed). Restored `05cc47b2` == HEAD. - **B. "Former owner still ships" guard removed** (blob `05cc47b2` → `322a4e8d`): `Tests 1 failed | 5 passed (6)`, the CONTROL case, the module served the App's edit instead of its own view. Restored `05cc47b2` == HEAD. </details> ## Acceptance notes - The card's acceptance is cloud-side (`composed-version-upgrade.test.ts` case 4); this PR pins the same three facts objectstack-side. The cloud test seeds a base-bound row, the shape case 3 covers. - `GET /packages/:id/customizations` (`listServedOverrides`) still lists a pair override under the package its declaration names; a moved override is served at the new owner but listed under neither until it is saved again. The item lock resolution is unchanged. Both are named in the report's open questions. ## Verification Head `0fad163d` (branch `claude/project-thread-jx84uj`). #31614 merged first; `origin/main` merged in at `a59426ee` (merge, no rebase), merge base `8784ccbc`. - **Build:** `pnpm install` 0; `turbo build --filter ./packages/**` 81/81 after the merge. - **Tests:** the new file 6/6; runtime `src/domains` slice 34 passed (this file, `meta-legacy-pair-served`, `packages-customizations`); `@objectstack/metadata-protocol` 327 files / 22593 tests passed; `@objectstack/objectql` 571 files / 11581 tests passed. Heavy runs through `os-verify-lock.sh` (VERDICT command-exit 0). - **Typecheck:** metadata-protocol 0, runtime 0. - **Lint:** eslint over the 4 changed sources and the test, exit 0. - **Gates:** `dispatch-gates --commands` derived 72 families for these 6 paths; all 72 run at `0fad163d`, each exit 0. `dispatch-gates --ran`: 72 derived, 72 run, 0 NOT-MEASURED, 0 UNRUN. One gate along the way: `check:query-options-erasure` first counted the test's untyped option bags (236 → 237); `0fad163d` types them (`EngineQueryOptions`, `DataEngineInsertOptions`) and it reads 236 again. - **Dogfood:** not owed (no `!` in the title, no narrowing arm: reads widen within one App, no accept set changes). - **`test:repo`:** declared to CI, not run locally. --- _Generated by [Claude Code](https://claude.ai/code/session_01ADHkmR2hWY1vfpcDK2q19Q)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 532230f commit 529e535

6 files changed

Lines changed: 532 additions & 12 deletions

File tree

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
'@objectstack/metadata-protocol': patch
3+
---
4+
5+
fix(metadata-protocol): a tenant's edit follows its item across the packages of one App
6+
7+
Clause-②: no — no accept set changes; the lookup scope widens only within one suite (triage 6724363147)
8+
Metadata-protocol: none
9+
10+
When a later version of an App moves an item from one of its packages to another (an ADR-0130 D4 suite split such as a flat App becoming an App plus modules, or the rollback of one), the item's reads no longer drop the tenant's edit of it. Before, the edit was still stored and every read served the vendor's item: the reads looked for the edit under the package that ships the item now, and the edit names the package that shipped it at the time of the edit.
11+
12+
- **What follows.** The App pair's (`<app>.custom`) declared override whose `overrides.package` names a former owner of the item, and a row bound to the former owner itself (a Regime O save stored before ADR-0146 D11 closed saves naming the read-only base). Each is served at the package that ships the item now, by name (with and without `?package=`), in the list and as the layered read's `effective`.
13+
- **Same App, read from the install record.** Two installed packages are of one App when `appOf` answers the same App for both over the registry's installed manifests. When the former owner is no longer installed (a rollback), the record is the row's binding to the App's pair, which the save door admits only for a package of that App. Never an id or name prefix.
14+
- **What never follows.** A row bound to, or declaring, a package of another App; a row of another App's pair; a row whose former owner still ships the item (it stays that package's edit); an `object` (it takes no tenant overlay, `ITEM_LOCKED`); and, between two former owners' rows, neither.
15+
- **Nothing is written.** The row keeps its binding and body, so a rollback needs no reverse step; the next save of the item writes the current owner's address, as every save does.

‎packages/metadata-protocol/src/protocol-environment-rows.ts‎

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ import type { StoredOverlayRow } from './item-lock.js';
5050
import { overlayMemberWhere } from './migrations/overlay-index.js';
5151
import type { ObjectStackProtocolImplementation } from './protocol.js';
5252
import { rowMatchesOverlayWhere, servedOverlayRowCandidates } from './protocol.js';
53-
import { isServedOverrideRowOf } from './served-override.js';
53+
import { holdsServedOverlayCandidate } from './protocol-overlay-merge.js';
5454
import { installedManifestsOfEngine } from './installed-manifests.js';
5555

5656
/**
@@ -64,6 +64,7 @@ export interface EnvironmentRowsHost {
6464
readonly engine: ObjectStackProtocolImplementation['engine'];
6565
readonly findEnvironmentRow: ObjectStackProtocolImplementation['findEnvironmentRow'];
6666
readonly findEnvironmentRows: ObjectStackProtocolImplementation['findEnvironmentRows'];
67+
readonly shippedArtifactsOf: ObjectStackProtocolImplementation['shippedArtifactsOf'];
6768
readonly legacyOrganizationRowsOf: ObjectStackProtocolImplementation['legacyOrganizationRowsOf'];
6869
readonly rethrowUnlessMetadataStoreUnprovisioned: ObjectStackProtocolImplementation['rethrowUnlessMetadataStoreUnprovisioned'];
6970
readonly withoutLegacyOrganizationRows: ObjectStackProtocolImplementation['withoutLegacyOrganizationRows'];
@@ -97,7 +98,7 @@ export async function findEnvironmentRow(this: Pick<EnvironmentRowsHost, 'engine
9798

9899
/** The body of `ObjectStackProtocolImplementation#findServedOverlayRow`; the method's docblock there is its contract. */
99100
export async function findServedOverlayRow(
100-
this: Pick<EnvironmentRowsHost, 'engine' | 'findEnvironmentRow'>,
101+
this: Pick<EnvironmentRowsHost, 'engine' | 'findEnvironmentRow' | 'findEnvironmentRows' | 'shippedArtifactsOf'>,
101102
args: {
102103
type: string;
103104
name: string;
@@ -119,7 +120,14 @@ export async function findServedOverlayRow(
119120
// read); with no package context (legacy/runtime reader), any row.
120121
const other = PLURAL_TO_SINGULAR[args.type] ?? SINGULAR_TO_PLURAL[args.type];
121122
const installed = installedManifestsOfEngine(this.engine);
122-
for (const candidate of servedOverlayRowCandidates({ packageId: args.packageId, servedOverride: args.servedOverride, installed })) {
123+
// [#31593] The code packages that ship the item, read once, when a
124+
// moved edit of it can be a candidate (`suite-moved-overlay.ts`).
125+
let shippers: ReadonlySet<string> | undefined;
126+
const ships = (packageId: string): boolean => {
127+
shippers ??= shippingPackageIds(this.shippedArtifactsOf(args.type, args.name, undefined, args.member));
128+
return shippers.has(packageId);
129+
};
130+
for (const candidate of servedOverlayRowCandidates({ packageId: args.packageId, servedOverride: args.servedOverride, installed, ships })) {
123131
const type = candidate.spelling === 'canonical' ? args.type : other;
124132
if (type === undefined) continue;
125133
const where: Record<string, unknown> = {
@@ -130,6 +138,17 @@ export async function findServedOverlayRow(
130138
...overlayMemberWhere(type, args.member),
131139
};
132140
if (candidate.packageId !== undefined) where.package_id = candidate.packageId;
141+
if (candidate.moved === 'base') {
142+
// [#31593] A row bound to a former owner of the item, at any
143+
// binding: every row of the item is read and the predicate
144+
// picks; two former owners' rows are never guessed between.
145+
const rows = activeRows !== undefined && args.state === 'active'
146+
? activeRows.filter((stored) => rowMatchesOverlayWhere(stored, where))
147+
: await this.findEnvironmentRows('sys_metadata', { where, context: { isSystem: true } });
148+
const moved = (rows ?? []).filter((row) => holdsServedOverlayCandidate(candidate, row, installed, ships));
149+
if (moved.length === 1) return { row: moved[0], overrideOf: candidate.overrideOf };
150+
continue;
151+
}
133152
// [#21911, ADR-0096] The explicit system opt-in: a platform store
134153
// read, which no caller's grants scope — the door that asked
135154
// already authorized the caller, and the protocol scopes the row
@@ -143,8 +162,10 @@ export async function findServedOverlayRow(
143162
// candidate only when it declares the override of the address's
144163
// package ({@link isServedOverrideRowOf}, on the row's own binding and
145164
// body): an undeclared row of the pair is the pair's own item.
165+
// [#31593] …and the moved-override candidate only when its body
166+
// declares a former owner of the item ({@link isMovedOverrideRowOf}).
146167
if (candidate.overrideOf !== undefined) {
147-
if (isServedOverrideRowOf(row, candidate.overrideOf, installed)) return { row, overrideOf: candidate.overrideOf };
168+
if (holdsServedOverlayCandidate(candidate, row, installed, ships)) return { row, overrideOf: candidate.overrideOf };
148169
continue;
149170
}
150171
return { row };

‎packages/metadata-protocol/src/protocol-overlay-merge.ts‎

Lines changed: 70 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import {
1212
type InstalledManifestLookup,
1313
} from '@objectstack/metadata-core';
1414
import { canonicalMetaUrlType } from '@objectstack/spec/shared';
15-
import { isServedOverrideRowOf } from './served-override.js';
15+
import { isServedOverrideRowOf, type ServedOverrideRow } from './served-override.js';
16+
import { isMovedBaseRowOf, isMovedOverrideRowOf, type ShipsName } from './suite-moved-overlay.js';
1617
import {
1718
listSlotNameOf,
1819
} from './protocol-object-extension-item.js';
@@ -39,6 +40,40 @@ interface ServedOverlayRowCandidate {
3940
* package's own item, never the base's.
4041
*/
4142
readonly overrideOf?: string;
43+
/**
44+
* [#31593] Set on the two candidates that hold a tenant's edit of the
45+
* item from before it moved to `overrideOf` from another package of the
46+
* same App (`suite-moved-overlay.ts`): `'override'`, a declared override
47+
* in the App's pair that names the former owner
48+
* ({@link isMovedOverrideRowOf}), at `packageId` the pair; `'base'`, a row
49+
* bound to the former owner itself ({@link isMovedBaseRowOf}), at any
50+
* package (`packageId` undefined). A row holds such a candidate only when
51+
* its predicate admits it, and it is then served as `overrideOf`'s item.
52+
*/
53+
readonly moved?: 'override' | 'base';
54+
}
55+
56+
/**
57+
* [#31593] Whether `row` holds `candidate`'s place beyond the `where` it
58+
* matched: the declared override when the candidate is one
59+
* ({@link isServedOverrideRowOf}), the moved edit when the candidate is one
60+
* ({@link isMovedOverrideRowOf}, {@link isMovedBaseRowOf}), and any row
61+
* otherwise. The one reading both readers of the candidates take
62+
* ({@link findServedOverlayRow}, {@link mergePackageAwareOverlay}).
63+
*/
64+
export function holdsServedOverlayCandidate(
65+
candidate: ServedOverlayRowCandidate,
66+
row: ServedOverrideRow,
67+
installed: InstalledManifestLookup | undefined,
68+
ships: ShipsName | undefined,
69+
): boolean {
70+
if (candidate.overrideOf === undefined) return true;
71+
if (installed === undefined) return false;
72+
if (candidate.moved === undefined) return isServedOverrideRowOf(row, candidate.overrideOf, installed);
73+
if (ships === undefined) return false;
74+
return candidate.moved === 'override'
75+
? isMovedOverrideRowOf(row, candidate.overrideOf, installed, ships)
76+
: isMovedBaseRowOf(row, candidate.overrideOf, installed, ships);
4277
}
4378

4479
/**
@@ -112,6 +147,14 @@ export function withoutServedRowName(item: unknown): Record<string, unknown> | u
112147
* the save never wrote (#23165's shape). A package-unbound row still wins
113148
* over the code package and over another package's row (ADR-0146 D8
114149
* item 1) until the migration moves it into the pair (D11).
150+
* - [#31593] …and, when the read also names which packages ship the name
151+
* (`ships`), a tenant's edit of the item from before it moved to the
152+
* package from another package of the same App (`suite-moved-overlay.ts`):
153+
* the App's pair's override that declares the former owner, right after
154+
* the package's own declared override; and a row bound to the former owner
155+
* itself, LAST, after the package-less row, which wins over another
156+
* package's row (D8 item 1). A read that names no `ships` (every write
157+
* address) is unchanged.
115158
*
116159
* Both readers of stored content take this order, so they cannot pick
117160
* differently: {@link ObjectStackProtocolImplementation.findServedOverlayRow}
@@ -133,13 +176,18 @@ export function servedOverlayRowCandidates(address: ServedOverlayAddress): Serve
133176
const pairs = address.servedOverride === true && typeof base === 'string' && base !== ''
134177
? pairsOf(base, address.installed)
135178
: [];
136-
const places: ReadonlyArray<Pick<ServedOverlayRowCandidate, 'packageId' | 'overrideOf'>> = base === null
179+
// [#31593] The moved edits are candidates only when the read names the
180+
// packages that ship the name (`ships`): the predicates need it.
181+
const moved = typeof base === 'string' && base !== '' && address.servedOverride === true && address.ships !== undefined;
182+
const places: ReadonlyArray<Pick<ServedOverlayRowCandidate, 'packageId' | 'overrideOf' | 'moved'>> = base === null
137183
? [{ packageId: null }]
138184
: base
139185
? [
140186
...pairs.map((pair) => ({ packageId: pair, overrideOf: base })),
187+
...(moved ? pairs.map((pair) => ({ packageId: pair, overrideOf: base, moved: 'override' as const })) : []),
141188
{ packageId: base },
142189
{ packageId: null },
190+
...(moved ? [{ packageId: undefined, overrideOf: base, moved: 'base' as const }] : []),
143191
]
144192
: [{ packageId: undefined }];
145193
return (['canonical', 'other'] as const).flatMap((spelling) =>
@@ -177,6 +225,12 @@ export type ServedOverlayAddress =
177225
readonly servedOverride: boolean | undefined;
178226
/** The installed manifests of the registry the read serves, read for the address's pair. */
179227
readonly installed: InstalledManifestLookup;
228+
/**
229+
* [#31593] Which code packages ship the item the read serves, in the
230+
* registry it serves. Named, the moved edits of the item are
231+
* candidates too (`suite-moved-overlay.ts`); omitted, they are not.
232+
*/
233+
readonly ships?: ShipsName;
180234
};
181235

182236
/**
@@ -359,20 +413,28 @@ export function mergePackageAwareOverlay(
359413
list: readonly Contribution[],
360414
real: string | undefined,
361415
): { item: any; overrideOf?: string } | undefined => {
416+
// [#31593] The code packages that ship the slot's item: its
417+
// lower-layer contributions, each under its own package.
418+
const shippers = new Set(list
419+
.filter((c) => c.stored === undefined && !c.standIn && c.pkg !== undefined)
420+
.map((c) => c.pkg as string));
421+
const ships = (packageId: string): boolean => shippers.has(packageId);
362422
for (const candidate of servedOverlayRowCandidates(options.installed !== undefined
363-
? { packageId: real, servedOverride: options.servedOverride, installed: options.installed }
423+
? { packageId: real, servedOverride: options.servedOverride, installed: options.installed, ships }
364424
: { packageId: real })) {
365-
const hit = list.find((c) => c.stored !== undefined
425+
const hits = list.filter((c) => c.stored !== undefined
366426
&& (c.stored.type === type ? 'canonical' : 'other') === candidate.spelling
367427
&& (candidate.packageId === undefined || (c.pkg ?? null) === candidate.packageId)
368428
// [#28409] The pair's row holds the override candidate only
369429
// when it declares the override: judged on the row's own
370430
// binding (the contribution's `pkg`, the row's `package_id`)
371431
// and its body, never on a stamp the body carries.
372-
&& (candidate.overrideOf === undefined || isServedOverrideRowOf(
373-
{ type: c.stored.type, package_id: c.pkg, metadata: c.item }, candidate.overrideOf,
374-
options.installed!,
375-
)));
432+
// [#31593] …and a moved candidate, when its predicate admits it.
433+
&& holdsServedOverlayCandidate(candidate,
434+
{ type: c.stored.type, package_id: c.pkg, metadata: c.item }, options.installed, ships));
435+
// [#31593] Two former owners' rows could follow the item: neither
436+
// is guessed (`suite-moved-overlay.ts`).
437+
const hit = candidate.moved === 'base' ? (hits.length === 1 ? hits[0] : undefined) : hits[0];
376438
if (hit) return candidate.overrideOf === undefined ? { item: hit.item } : { item: hit.item, overrideOf: candidate.overrideOf };
377439
}
378440
return undefined;

‎packages/metadata-protocol/src/protocol.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3814,6 +3814,8 @@ export class ObjectStackProtocolImplementation implements
38143814
const host: ThisParameterType<typeof findServedOverlayRow> = {
38153815
get engine() { return readEngine(); },
38163816
findEnvironmentRow: (...a) => this.findEnvironmentRow(...a),
3817+
findEnvironmentRows: (...a) => this.findEnvironmentRows(...a),
3818+
shippedArtifactsOf: (...a) => this.shippedArtifactsOf(...a),
38173819
};
38183820
return findServedOverlayRow.call(host, args, activeRows);
38193821
}

0 commit comments

Comments
 (0)