Skip to content

feat(console): entering an app loads its metadata bundle once, and a record open reads no metadata - #28321

Merged
objectstack-fleet[bot] merged 9 commits into
mainfrom
claude/project-thread-xoio4p
Oct 31, 2026
Merged

objectstack-fleet[bot] merged 9 commits into
mainfrom
claude/project-thread-xoio4p

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

domain:ui#2 · os-tesla · session_01L9qibzFu8iUogiZhxL9tAo

Requested by Tesla · project thread

Fixes #27627

Clause-②: yes (widening)
Metadata-protocol: none

维护者速读

  • 改了什么:控制台进入一个应用时,用一个请求取回该应用的元数据包(GET /api/v1/meta/app/{appId}/bundle)。之后包里有的对象、视图、页面、仪表盘和应用本身,一律从包里读,不再逐个请求。再次进入应用时带 If-None-Match,服务端回 304 就继续用手上这份。
  • 为什么改:Seam: one request opens a record — a record-page payload (record + related counts and first rows + field access + lock) and an app-scoped metadata bundle, so a record open is 2–3 requests instead of 23–45 serialized in three stages #27627 的 Target 3 元数据半边(「27627 插队」)。PR 1 之后,打开一条记录还剩第二波 /meta/object/… 读取。本 PR 把这一波去掉了:热会话打开记录只发 1 个页面请求、0 个元数据请求;冷会话最多再加 1 个元数据包请求,而且和页面请求同一波发出。改前 main 上是 1 个页面请求加 5 个 /meta/object,分两波。
  • 风险与回滚:只改 ui 的 data-objectstack 和 app-shell;对外只新增适配器的一个公开方法 loadAppBundle(条款② 放宽)。包里没有的条目照旧走单条路由,包括 Studio、应用以外的对象,以及调用者无权读、因此不在包里的条目。任何经客户端的元数据写入都会让包先重新校验再答下一次读取;切换组织或语言会清空缓存,包随之重新下载。服务端没有这条路由或拒绝时,行为和现在一样。回滚即 revert 本 PR。
  • 先看:ui/packages/data-objectstack/src/app-bundle.ts(包的存放、校验与失效);index.ts 里的 installBundledItemReads 和 fetchObjectSchemaFresh(两个读取入口);app-shell/src/console/AppContent.tsx 里的 loadAppBundle 调用(只对 URL 指名的应用)。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/app-shell/src/views/RecordDetailView.recordPagePayload-27627.test.tsx packages/data-objectstack/src/appBundle-27627.test.ts packages/app-shell/src/console/__tests__/AppContent.loadAppBundle-27627.test.tsx,预期 3 个文件、21 个测试全绿。
  • 需要你做的:无,席位落地(生产方 PR feat(rest): one request loads an app's metadata — GET /meta/app/{appId}/bundle, per caller, with an ETag #28287 已合并)。

What changes

The console's metadata reads on app entry and on a record open, measured on main 69ce01d7 through the record page's own acceptance transport (RecordDetailView.recordPagePayload-27627, StrictMode, the real adapter):

open before (this transport, main) after
cold (first record in the session) POST …/page + 5 × GET /meta/object/NAME, in two waves (the contact's own definition in wave 2) POST …/page + 1 × GET /meta/app/APP/bundle, both in wave 1
warm, the app entered earlier, no record opened yet the same 5 by-name reads POST …/page alone, 0 metadata requests

Load once, revalidate after (ui/packages/data-objectstack/src/app-bundle.ts, ObjectStackAdapter.loadAppBundle):

  • loadAppBundle(appId) asks GET /api/v1/meta/app/{appId}/bundle through the client's conditional meta.getAppBundle(appId, { ifNoneMatch }) (feat(rest): one request loads an app's metadata — GET /meta/app/{appId}/bundle, per caller, with an ETag #28287). Calling it again for an app already held sends If-None-Match with the held validator as the server spelled it (the producer's weak W/"…" included, rebuilt from the client's etag: { value, weak }), and a 304 (notModified) keeps the bundle. An author-set If-None-Match makes the browser pass the server's 304 through rather than answer from its own cache.
  • One bundle is kept per app entered this session. The last app entered answers reads, and the others are kept so that re-entering one revalidates it instead of downloading it again.
  • The body is read as the producer declares it, typed by AppMetadataBundleResponse (z.input of AppMetadataBundleResponseSchema, @objectstack/spec/api): bare, with every member the GetMetaItemResponse envelope its own GET /meta/{type}/{name} returns. Each envelope is kept whole and handed back as that read's answer.

Serve the by-name reads of bundled items (index.ts):

  • installBundledItemReads wraps the client's meta.getItem(type, name), the one door that getView, getPage, getApp, getItems, probeMetaItemAccess and app-shell's MetadataProvider (through getClient()) all go through. An item the active bundle holds is answered there, as a copy of its envelope. A call with options, and any item the bundle does not hold, goes to the route.
  • fetchObjectSchemaFresh, the adapter's raw GET /meta/object/NAME, now asks the bundle first. The unwrap, disabledActions and sortability stamping that follow are unchanged, so a bundled object carries the same symbols its by-name read gave it.
  • A read made while the bundle is in flight waits for it, so a record opened as the app is entered sends no by-name read for anything the bundle holds.

Staying current:

  • After a metadata write through any of the client's write doors (saveItem, deleteItem, publishItem, rollbackItem) settles, the bundles are marked stale. So is any invalidateCache(key) whose key names a held item, in the cache-key spellings the adapter and its hosts use (a bare object name, view:OBJECT:NAME, TYPE:NAME from app-shell MetadataService, and list:TYPE, which covers every item of that type). The same applies to invalidateViewKeys for a held view.
  • A stale bundle, or one older than the adapter cache's lifetime, revalidates with If-None-Match before it answers its next read. That read never goes by name, so there is no dual path for a bundled item.
  • clearCache() (organization, session or locale switch) drops every bundle's items and validator, and the next read downloads the bundle again. The response varies on Accept-Language, and this is what makes a locale switch refetch.

App entry (AppContent.tsx): an effect calls loadAppBundle(matchedApp.name) when the URL names an app. That holds on the package-id route the sidebar builds and on the name alias. A pseudo-route's fallback app is not entered. A host adapter without loadAppBundle is left alone (protocol-agnostic, AGENTS #1).

Unchanged: list reads (GET /meta/object, /meta/view, /meta/app, the package-scoped object list) keep their routes, and so do the preview (?preview=draft) client and Studio's MetadataClient.

Tests

  • ui/packages/data-objectstack/src/appBundle-27627.test.ts (new, 11), through the real adapter and client over a held transport:
    • a read made while the bundle loads waits, and nothing it holds (object, child object, view, page, app, dashboard) is asked by name;
    • an unheld item and a read with options keep the route;
    • re-entry sends If-None-Match, and a 304 keeps what is held;
    • an invalidation naming a bundled object asks the bundle again (with the validator), not the item;
    • an invalidation naming nothing held asks nothing;
    • a metadata save makes the next bundled read revalidate;
    • a cache clear re-downloads with no validator;
    • a bundled object carries its envelope's sortability;
    • a weak validator goes back byte-for-byte, W/ included;
    • a bundle served no-store answers the reads that waited on it and is not kept: the next read of a member goes by name, and re-entry downloads it with no validator;
    • a refused bundle (the app read's own 403) leaves reads on their routes.
  • RecordDetailView.recordPagePayload-27627.test.tsx, two cases added (the card's acceptance):
  • AppContent.loadAppBundle-27627.test.tsx (new, 2), on a real AppContent mount: the package-id route and the name alias each load the bundle by the app's name, once.
  • Three AppContent verdict files (deniedVsUnpublished, settingsMarketplaceVerdict-27507, setupItemRoutesVerdict-12706) count by-name app probes with a regex that also matched the new bundle URL. Their transports now answer the bundle as refused before the probe regex runs. The probe assertions themselves are unchanged.

Acceptance notes

  • Measured deviation: withheld vs outside. The order reads "an item withheld from the caller is answered as withheld, not fetched by name". The producer's bundle (feat(rest): one request loads an app's metadata — GET /meta/app/{appId}/bundle, per caller, with an ETag #28287) names nothing it withheld: a withheld item is simply absent, indistinguishable from an item outside the app. So the console cannot tell the two apart, and an absent name keeps its by-name read, which answers that item's own refusal. Measured in appBundle-27627.test.ts (an unheld item goes to its route). The seat and spec seat 1 agreed this reading.
  • Through the client, typed by the spec. With feat(rest): one request loads an app's metadata — GET /meta/app/{appId}/bundle, per caller, with an ETag #28287 on main, requestAppBundle calls the client's meta.getAppBundle(appId, { ifNoneMatch }) and indexAppBundle takes AppMetadataBundleResponse. The client reports the validator as { value, weak } with the quotes and W/ taken off, so the adapter rebuilds W/"value" (or "value") to send back; for a well-formed entity tag that is the header byte-for-byte (pinned with a W/"sha256-…" tag). The client does not expose Cache-Control, so a 200 with no ETag is read as the producer's no-store answer, the only one it serves without a validator.
  • Clause-② re-measured on the built declarations: yes (widening). The dispatch expected no. Built ui/packages/data-objectstack/dist/index.d.ts gains one public member on the exported ObjectStackAdapter, loadAppBundle(appId: string): Promise<void> (plus the private appBundles / requestAppBundle, which consumers cannot reach). AppBundleStore and indexAppBundle are not exported. @object-ui/app-shell's built declarations name nothing new. Metadata-protocol: none stands: nothing a metadata author writes changes, so the changeset stays patch (AGENTS.md Post-Task Checklist step 3).
  • Only the URL-named app is entered. AppContent loads matchedApp, not activeApp, so a pseudo-route (Setup, marketplace) that falls back to a default app does not load that app's bundle. This also keeps the three verdict suites' probe counts honest.
  • The 动态 stream (feat(plugin-detail)!: one record page laid out by its own width; 动态 is the right column's first panel #28116) is not metadata. Since feat(plugin-detail)!: one record page laid out by its own width; 动态 is the right column's first panel #28116 the record page's right column opens with the 动态 panel, which reads sys_comment, sys_activity and the mention list (sys_user) beside the page request. Those are data reads, pinned by main's own cases; the acceptance cases here count them separately and assert 0 metadata requests and exactly one page request.
  • Revalidation follows the adapter cache's lifetime. A bundle answer stands for cache.ttl (default 5 min), like the by-name answers it replaces. Past that, the next bundled read revalidates with If-None-Match first, so a warm open after the lifetime costs one 304 round trip, not five by-name reads.
  • A no-store bundle is not kept. When a member's field visibility is undetermined the producer serves private, no-store and no ETag; the adapter reads the missing ETag. The bundle answers the reads that were waiting on it (the open in progress), and then the store holds nothing for that app. Later reads go by name until the app is entered again, which downloads it with no validator. The adapter's own MetadataCache still keeps an object definition it read during that open for its lifetime, exactly as it does for a by-name answer today; this PR does not change that cache.
  • Live boot: NOT MEASURED. The route is on main since feat(rest): one request loads an app's metadata — GET /meta/app/{appId}/bundle, per caller, with an ETag #28287, but no live server was booted for this PR. The acceptance counts are measured through the record page's real adapter over its acceptance transport, which now serves the bundle with a weak ETag and private, no-cache, as the route does. A server without the route answers 404, and the console behaves as it does today (pinned: a refused bundle leaves every read to its route).
  • Landing order: producer PR feat(rest): one request loads an app's metadata — GET /meta/app/{appId}/bundle, per caller, with an ETag #28287 merged (8441a012). This PR is the card's last part, so it says Fixes #27627. The seat lands it.

Verification

All on head 07e67218. Producer #28287 (8441a012) and the rest of main were merged in, then the switch to the client's getAppBundle and the spec type, then the changeset's Clause-②: yes (widening).

  • Gates: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 67 commands (the same set as on every earlier head). All 67 were run with </dev/null, and every one exited 0. Reconcile --ran exit 0: 67 derived, 67 run, 0 NOT-MEASURED. Also pnpm --dir ui run check:new-line-citations exit 0 (0 new citations) and pnpm check:nul-bytes exit 0. node scripts/check-sdui-manifest.mjs is in the derived list.
  • Built after pnpm install --frozen-lockfile, with a declarations build of the @object-ui/app-shell closure and @objectstack/client-react....
  • Type-check:
    • ui/packages/data-objectstack tsc --noEmit exit 0.
    • ui/packages/app-shell tsc --noEmit exit 0 and tsc -p tsconfig.test.json exit 0.
  • Clause-② measure: the built ui/packages/data-objectstack/dist/index.d.ts names loadAppBundle(appId: string): Promise<void> as the one new public member. ui/packages/app-shell/dist declarations name no bundle symbol.
  • Suites: pnpm --dir ui exec vitest run packages/data-objectstack packages/app-shell/src/console packages/app-shell/src/views/RecordDetailView ran 419 files and 3336 tests, all green. That includes the 3 card files (21 tests) and the three AppContent verdict files.
  • Ablations through node scripts/ablation-replace.mjs. Each was restored with the blob equal to HEAD and an empty diff.
    • On this head:
      • With the weak flag dropped from the rebuilt validator, 4 cases go red, the weak-validator pin among them.
      • With the no-ETag branch off (the bundle kept), the no-store case goes red.
    • On earlier heads, with the same code:
      • With the bundle read off in fetchObjectSchemaFresh, 7 tests go red.
      • With the app-entry loadAppBundle call off, both AppContent pins go red.
      • With the write-door staleness off, the metadata-write case goes red.
  • Merge state against origin/main: clean.
  • Diff: 12 files, +923/−32.

Generated by Claude Code

…e app bundle

loadAppBundle(appId) reads GET /meta/app/{appId}/bundle once per app entry
and revalidates with If-None-Match; an object, view, page, dashboard or app
the active bundle holds is answered from it and never asked by name.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibzFu8iUogiZhxL9tAo
…en reads no metadata

The bundle's members are read as the by-name envelopes the spec's
AppMetadataBundleResponse declares; acceptance pins a warm record open at one
page request and zero metadata requests, a cold one at one bundle request.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibzFu8iUogiZhxL9tAo
…ct transports answer the bundle

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibzFu8iUogiZhxL9tAo
…tor goes back as served

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibzFu8iUogiZhxL9tAo
…-xoio4p

# Conflicts:
#	ui/packages/app-shell/src/views/RecordDetailView.recordPagePayload-27627.test.tsx
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#2 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz

Contract-review-claim: session_01L9qibw58ehQ2gRHP2h2Puz · head 07e672183d2728b1ac2d51cae149ab00772f7ece · 2026-10-31T11:14Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#2 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 07e672183d2728b1ac2d51cae149ab00772f7ece
Local-runs: none
Scope: ui/packages/data-objectstack/src/app-bundle.ts (AppBundleStore: enter, read, holds, holdsType, markStale, clear, ask; indexAppBundle), ui/packages/data-objectstack/src/index.ts (ObjectStackAdapter.loadAppBundle, requestAppBundle, installBundledItemReads, fetchObjectSchemaFresh reading the bundle first with requestObjectSchema split out, cacheKeyNamesBundledItem, the invalidateCache / invalidateViewKeys / clearCache hooks), ui/packages/app-shell/src/console/AppContent.tsx (the loadAppBundle effect on the URL-named app), the README and content/docs/frontend/data-objectstack.mdx prose, the three new test files and the three adapted verdict suites, .changeset/27627-console-app-metadata-bundle.md

Inputs: card #27627 (the Seam card; the console-half claim 6538126238 split beside the producer claim 6538108232; the amendments 6541997127 (producer #28287 merged, closing keyword Fixes, owed: merge main, switch to the conditional getAppBundle, bind AppMetadataBundleResponseSchema) and 6542521727 (Clause-② re-measured yes (widening) on the built declarations); the dev reports 6539052883 and 6542507828 with the withheld-vs-outside deviation the seat and spec seat 1 agreed), PR #28321 (body with the before and after request table measured through the record page's acceptance transport, the acceptance notes, the file list of 12 files, the diff of 955 lines against main read whole for every hand-written hunk, 9 commits from the merge-base), the 68 check-runs on this head (read 2026-10-31T11:17Z: all 68 runs completed, none failed, none cancelled, none in progress; success: Build & E2E UI, Build Docs, Build UI Deps, Check Changeset, Check Documentation Links, Dogfood Regression Gate, Governed Surface Queue Guard, Lint & Repo Gates, Lint UI, Lint · CI, release & engine gates, Lint · ESLint, Lint · PM dispatch-gates, Lint · docs & code guards, Lint · repo & tooling gates, Lint · scoped · query-options erasure, Lint · scoped · slot lookup & corpus, Lint · scoped · tooling self-tests, Live E2E UI, PR-body guards (claim · duplicate · Part-of · single-writer path), Plan Test Core shards, Spec property liveness, Test Core, Test Core (all 8), Test UI, Test UI (all 16), Type Check UI, Type Check · consumer gates, Type Check · debt ledger, Type Check · source gates, Type Check · workspace, TypeScript Type Check, filter; skipped: 18.x upgrade harness (opt-in), Auto Label, Build Core, Check PR Size, Dogfood Regression Gate (matrix shard/3), Dogfood Verify CLI, Packed-tarball smoke (opt-in), Schedule gate · skip when a green push run covers the commit, Temporal Conformance (live PG + MySQL), UI Published Artifacts), and the tree on main for the producer (#28287 merged as 8441a01: packages/client meta.getAppBundle(appId, { ifNoneMatch }) answering { data, etag: { value, weak }, notModified }, packages/spec/src/api/app-bundle.zod.ts AppMetadataBundleResponseSchema, the bare body with app, objects, views, pages, dashboards as GetMetaItemResponse envelopes, the per-caller read gate and the weak ETag, no-store when a member's field visibility is undetermined, as the lane's PASS 6540468220 on that PR recorded), the adapter's existing installViewListShare wrap shape and MetadataCache lifetime. The head is mergeable and clean against today's main; RecordDetailView.tsx moved on main since the merge-base (#28392) outside this PR's one test file on that view.

① Derived judgments

  • One door, no dual path for a bundled item - right. client.meta.getItem(type, name) is the one door getView, getPage, getApp, getItems, probeMetaItemAccess and app-shell's MetadataProvider go through, and installBundledItemReads answers it from the active bundle as a copy of the item's own envelope, falling through to the route for an item the bundle does not hold or any call with options; fetchObjectSchemaFresh, the one read outside the SDK, asks the bundle first and keeps its unwrap and disabledActions / sortability stamping, so a bundled object carries what its by-name read gave it (pinned on sortability). A read made while the bundle is in flight waits for it, which is what makes the cold open one wave.
  • The bundle is the caller's own, and absence is answered by the route - right, and it is the right reading of the order's "withheld" line. The producer builds every member through the same per-caller gate and names nothing it withheld, so an absent name cannot be told from an item outside the app; the adapter sends it by name, and the route answers that item's own refusal. No item is answered from a bundle it is not in, and no refusal is invented client-side.
  • Staying current is bounded by the server's validator - right. A write through any client write door marks every bundle stale once it settles, whatever it wrote; an invalidateCache(key) naming a held item in any of the cache-key spellings the adapter and its hosts use (bare object name, view:OBJECT:NAME, TYPE:NAME, list:TYPE), an invalidateViewKeys of a held view, or an answer older than the adapter cache's lifetime makes the next bundled read revalidate first with If-None-Match, the validator rebuilt byte-for-byte from the client's { value, weak } (W/ included, pinned). A 304 keeps the bundle; clearCache() (organization, session, locale) drops items and validator so the next read downloads with no validator, which is what makes a locale switch refetch a body that varies on Accept-Language. An answer that arrives after a clear() belongs to the old world and is dropped.
  • no-store is honoured without a Cache-Control read - right. The client exposes no Cache-Control, and the producer serves exactly one 200 without an ETag, its no-store answer; the adapter reads the missing validator as that case, answers the reads waiting on it, and keeps nothing, so a later read goes by name until the app is entered again (pinned). The adapter's own MetadataCache still keeps a definition read during that open for its lifetime, as it does for a by-name answer today, which this PR does not change.
  • Only the URL-named app is entered - right. AppContent loads matchedApp.name by the app's name, as the route addresses it; a pseudo-route's fallback app is not entered, so Setup and the marketplace load no bundle, and a host adapter without loadAppBundle is left alone. The three verdict suites' probe counts were protected by answering the bundle as refused before the probe regex runs, with their assertions unchanged.
  • The widening is what the declaration of record says - right. The built @object-ui/data-objectstack declarations gain one public member, ObjectStackAdapter.loadAppBundle(appId: string): Promise<void>; AppBundleStore and indexAppBundle are not exported, and app-shell's declarations add nothing. Nothing a metadata author writes changes and no spec path moves, so Metadata-protocol: none holds.
  • Pins both ways - right. Eleven cases through the real adapter and client over a held transport, two acceptance cases on the record page's real adapter (cold: one bundle read and one page request in wave one; warm: one page request and zero metadata requests, the 动态 stream's data reads counted apart), two on a real AppContent mount, and five ablations across heads each reddening the cases they should and restored byte-identical.
  • Check-runs as gate verdicts - read as above; every run on this head completed and none is red, cancelled or in progress at the read.

② Semver level

  • @object-ui/data-objectstack and @object-ui/app-shell patch - accepted. A ui package takes patch for a non-protocol widening; Clause-②: yes (widening) names the one new public member, and nothing is narrowed.

③ Boundary flags

  • Live boot not measured - the acceptance counts come from the record page's real adapter over its acceptance transport, which serves the bundle with a weak ETag and private, no-cache as the route does; no live server was booted. A server without the route answers 404 and every read keeps its route (pinned). Worth one live open on a cloud box when the seat next has one, recorded on the card, not owed before landing.
  • A bundled read older than the cache lifetime costs one 304 round trip - by design, the same lifetime the by-name answers it replaces had; not a regression.
  • Studio, the preview client and list reads are unchanged - as the body says; the bundle serves by-name item reads only.
  • The seat lands - no Tier H path (no docs/adr, no skills/**, 955 lines), no human gate; the spec names it binds were approved by the maintainer at 09:30Z (6541747154).

④ Design judgment

  • Tried: two apps entered in one session (the last entered answers reads; re-entering the first revalidates, not downloads); an invalidation key of a type the bundle holds none of (holdsType false, nothing marked); a write door that rejects (finally still marks stale, which is the safe side); a getItem with an options object of {} (goes to the route, by the undefined test, which is the conservative reading). Keying the whole thing on the server's validator and gate, with no client-side guess about withheld items, is what keeps this consumer from inventing authority it does not have. Nothing to add.

Verdict reasoning. The console enters an app with one request and from then on answers every by-name read of a bundled item from the caller's own bundle through the one door those reads share, with absence left to the route, currency bounded by the server's validator through every write and invalidation door the adapter has, no-store honoured, only the URL-named app entered, the one new public member declared with the right level, the pins measuring the card's acceptance on the real adapter, and every check on this head green or skipped.

Implemented-by: claude/project-thread-xoio4p
Reviewed-by: session_01L9qibw58ehQ2gRHP2h2Puz

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#2 · os-tesla · session_01L9qibxgfM8p8t3fo6y9eFK

维护者速读

席位核对

ACCEPT · head 07e672183d2728b1ac2d51cae149ab00772f7ece · 2026-10-31T11:18Z

  1. 形态: 12 files, +923/−32; Fixes #27627; 0 governed paths, 955 lines ≤ 3000 (check-governed-merges --pr 28321).
  2. 前提实测: producer feat(rest): one request loads an app's metadata — GET /meta/app/{appId}/bundle, per caller, with an ETag #28287 on main as 8441a01277 (consumer-after-producer cleared, amendment 6541997127); the adapter calls the conditional meta.getAppBundle(appId, { ifNoneMatch }) and binds AppMetadataBundleResponse (the maintainer-approved spec name).
  3. 门禁 check-runs: 68 on the head, 54 success + 14 skipped, 0 failing; mergeable_state clean.
  4. 契约复审: Clause-②: yes (widening) on claim amendment 6542521727; PASS 6542690057 at CONTRACT_REVIEW_TIER on this head, nothing blocking.
  5. 范围外去向: one live open on a cloud box (review ③), recorded on the card after landing, not owed before. Skills-docs: n/a (widening arm, Metadata-protocol: none).
  6. 落地条款: the seat runs pr_ready and auto-merge once the maintainer gives the word in the seat thread.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 31, 2026 14:23
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 31, 2026
Merged via the queue into main with commit a9f1452 Oct 31, 2026
72 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-xoio4p branch October 31, 2026 14:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

1 participant