You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
os dev over a HOST config composes two registrars for stack-declared security metadata — AppPlugin over the config module and the dev-only HMR MetadataPlugin over dist/objectstack.json #14397
Found while implementing #12892 step 2 (dev seat, session session_016yfqQh2dBgPAymYd7xipza, branch claude/issue-12892-artifact-boot-single-registrar). Filed unassigned for triage; #12892 is not addressed by this card and this card is not addressed by that PR — that PR unifies the ARTIFACT boot (createStandaloneStack), and this is a different boot shape with the same two-writer class.
Mechanism (read from source on origin/main1dcb995f23; not driven)
packages/cli/src/commands/serve.ts, boot-mode dispatch: shouldBootWithLibrary(config) (packages/cli/src/utils/plugin-detection.ts) returns false for a HOST config — one whose plugins[] holds instantiated plugins (isHostConfig) — so the standalone stack, and with it the artifact door, is never composed. Then:
serve step 3 (around line 2811): when the host config ALSO carries top-level metadata and brings no AppPlugin of its own, new AppPlugin(config) wraps the loaded module. Its ADR-0057 block registers positions / permissions / capabilities / sharingRules into the metadata service from the MODULE — forward-converted, not strict-parsed, no schema defaults, no ADR-0010 provenance (the default 'app-plugin' registrar).
the dev-only metadata-HMR block (around line 3024): under isDev && flags.server && !hasMetadataPlugin, when resolveDefaultArtifactPath(readInternalArtifactPath()) resolves — i.e. when the os dev supervisor has compiled dist/objectstack.json — a MetadataPlugin({ watch: false, artifactWatch: true, artifactSource: { mode: 'local-file', path } }) is composed. That is the artifact door: it strict-parses, forward-converts and ADR-0010-stamps the COMPILED TWIN of the same stack and registers all four security collections too.
⇒ On that boot the same declarations reach the metadata service through two writers over two SOURCES of one stack (the module and its compiled artifact); the copies differ on the parse axis exactly as measured on #12892 (a sharing rule's condition is a bare string on the module copy and { dialect, source } on the door's; a capability has no scope default and no _packageVersion on the module copy). Which copy a consumer reads depends on start order. The HMR door additionally RELOADS its copy on every recompile while the module copy never refreshes, so the winner can change mid-run.
Not measured here — the two readings a triage needs
Whether os dev compiles a host config into dist/objectstack.json at all (if it never does, hmrArtifactPath never resolves and the second door is never composed — the path would be dead rather than double).
Which copy wins on this boot. The HMR MetadataPlugin is kernel.used after the app wrap, so positionally the door's copy would land last (the opposite of the artifact boot, where AppPlugin won) — an inference from list order, not a measurement.
Make the dev-only HMR door register NOTHING under the four security kinds (a door option) — engine lane, and it inverts the ownership the maintainer chose for the artifact boot.
git grep -n "hasMetadataPlugin\|shouldBootWithLibrary\|hasAppPluginAlready" -- packages/cli/src/commands/serve.ts
git grep -n "export function isHostConfig\|export function shouldBootWithLibrary" -- packages/cli/src/utils/plugin-detection.ts
Duplicate check
search_issues, one targeted query: 13 hits, none covering this boot shape; the control fired (the parent class card #12844 came back at rank 3, so the query reached the right corpus).
Refs: #12892 (the artifact-boot half — step 2 landing as its PR), #12844, #7049.
Found while implementing #12892 step 2 (dev seat, session
session_016yfqQh2dBgPAymYd7xipza, branchclaude/issue-12892-artifact-boot-single-registrar). Filed unassigned for triage; #12892 is not addressed by this card and this card is not addressed by that PR — that PR unifies the ARTIFACT boot (createStandaloneStack), and this is a different boot shape with the same two-writer class.Mechanism (read from source on
origin/main1dcb995f23; not driven)packages/cli/src/commands/serve.ts, boot-mode dispatch:shouldBootWithLibrary(config)(packages/cli/src/utils/plugin-detection.ts) returnsfalsefor a HOST config — one whoseplugins[]holds instantiated plugins (isHostConfig) — so the standalone stack, and with it the artifact door, is never composed. Then:AppPluginof its own,new AppPlugin(config)wraps the loaded module. Its ADR-0057 block registerspositions/permissions/capabilities/sharingRulesinto the metadata service from the MODULE — forward-converted, not strict-parsed, no schema defaults, no ADR-0010 provenance (the default'app-plugin'registrar).isDev && flags.server && !hasMetadataPlugin, whenresolveDefaultArtifactPath(readInternalArtifactPath())resolves — i.e. when theos devsupervisor has compileddist/objectstack.json— aMetadataPlugin({ watch: false, artifactWatch: true, artifactSource: { mode: 'local-file', path } })is composed. That is the artifact door: it strict-parses, forward-converts and ADR-0010-stamps the COMPILED TWIN of the same stack and registers all four security collections too.⇒ On that boot the same declarations reach the metadata service through two writers over two SOURCES of one stack (the module and its compiled artifact); the copies differ on the parse axis exactly as measured on #12892 (a sharing rule's
conditionis a bare string on the module copy and{ dialect, source }on the door's; a capability has noscopedefault and no_packageVersionon the module copy). Which copy a consumer reads depends on start order. The HMR door additionally RELOADS its copy on every recompile while the module copy never refreshes, so the winner can change mid-run.Not measured here — the two readings a triage needs
os devcompiles a host config intodist/objectstack.jsonat all (if it never does,hmrArtifactPathnever resolves and the second door is never composed — the path would be dead rather than double).MetadataPluginiskernel.used after the app wrap, so positionally the door's copy would land last (the opposite of the artifact boot, whereAppPluginwon) — an inference from list order, not a measurement.Options as this seat sees them (none prejudged)
securityMetadataRegistrar: 'artifact-door'(the option Route ownership for the five artifact security collections — MEASURED: the two registrars' copies differ in TYPE on a key a consumer can read today, not just on some future retired key #12892 step 2 adds toAppPlugin). One route, one owner, on this boot shape too; costs a serve.ts edit and a measurement of the two readings above.serve.tsnamesapps/objectos's dev-workspace stack as the host-config case).Re-check
Duplicate check
search_issues, one targeted query: 13 hits, none covering this boot shape; the control fired (the parent class card #12844 came back at rank 3, so the query reached the right corpus).Refs: #12892 (the artifact-boot half — step 2 landing as its PR), #12844, #7049.
Generated by Claude Code