Skip to content

[Epic] ADR-0025 plugin package distribution — the next development phase: M0 record v2, M1 local T0 install loop (first-party, self-signed), M2 cloud fetch + per-plugin permission enforcement, M3 third-party sandbox tier #27108

Open

Description

@objectstack-fleet

Filing gate: ③ maintainer-directed. The maintainer, in the director summon chat on 2026-10-28, on ADR batch 4 (objectstack#26553): 「ADR-0025 插件包分发(代码 + 依赖)(2026-05,2026-09-12 审计) 应该是下一阶段的重点开发,需要综合评估和我沟通」; after the seat's assessment (code inventory on objectstack f2fdac5d7a and cloud ce05c91, decision points Q1–Q8): 「插件包分发 同意,应该立专题卡吧?」. Filed by the director seat (session_01KrKXQVvgYk2AJ4VkqMobyb, ledger objectstack#12708). ⛔ Not a claim. ⛔ No domain:*: the children span cli, server (core/runtime), cloud and spec; triage sets each child's lane. Dedupe: #17757 (closed, "ADR-0025 install-flow design"), #13457 (closed, F4 load-time registration), the retired #17147 / #11333 numbers in code comments; no open card carries the install loop. New.

维护者速读

ADR-0025 今天是「造得出、签得了、发得上去,但装不进来、跑不起来、拦不住」:供给侧(.osplugin 格式、os plugin build/sign/publish、云端审核与反签、安装时授权同意)完整;需求侧从 §3.5 第 4 步起不存在——没有 os plugin install、没有 .osplugin 加载器、云端没有 blob 下载路由、授权集只登记不拦截(pin test 钉着)、信任层 T0/T1/T2 只是枚举。本专题按你同意的席位倾向分四期(M0–M3),每期立子卡;M0 先改记录,再按 v2 开工。

The decisions this epic carries (the maintainer's 「同意」 to the seat's inclinations, 2026-10-28)

# Question Decision
Q1 first consumer who does the next phase close the loop for first-party drivers / connectors (T0, self-signed); the cloud plugins/connector-stripe sample is the acceptance case; third parties wait for M3
Q2 local-first local install before cloud fetch local first (os plugin install ./x.osplugin, ADR-0016 §9 parity); the cloud blob download route follows in M2
Q3 trust tier which tiers in M1 T0 only (node, first-party / verified), with the tier dispatch point left in the loader for T1 in M3
Q4 enforcement per-plugin SecurePluginContext M2, before any third-party load; services + hooks are enforced; the fs / network classes keep their schema and get their enforce-or-remove decision in M3 (no production call surface today)
Q5 registry sys_plugin* vs sys_package* accept the fold into sys_package*; v2 rewrites §3.8
Q6 dependencies manifest-deps drop it; bundled only (ADR-0049 enforce-or-remove; build-side half with zero installers)
Q7 §3.10 #4 / #5 RLS-scoped grants, secret-refs secret-refs only, when the first real connector needs them (M3); RLS-scoped grants stay a design note
Q8 §7 open questions key rotation, widening UX, hot vs boot, T2 RPC, dependency conflicts widening UX = the shipped 409 re-consent, not activated before consent; hot reload retired with #12587 → boot activation; key rotation, T2 RPC and dependency conflicts deferred to M3

Milestones (each a child card; Blocked-by: chains as listed)

  • M0 — ADR-0025 v2 (docs/adr/**, Tier H, one PR). Rewrite the four stale sections (§3.8 registry → sys_package*; §3.4 endpoint /cloud/packages; §3.3 esbuild; §3.10 L1 = script bodies + QuickJS, already live), fold Q1–Q8 in, split §3.5 steps 4–7 into dispatchable sub-steps, and set the status line: Accepted — narrowed to Phase 1 + consent + the M0–M3 plan. Authoring is open to every seat; the director seat drafts it unless triage routes otherwise.
  • M1 — local T0 install loop (Blocked-by: M0). (a) os plugin install ./x.osplugin: whole-artifact Ed25519 verification against the host's trusted keys (verifyPluginArtifact, today 0 callers) + per-file integrity re-verification at unpack, materialize to <OS_HOME>/plugins/<env>/<id>/<version>/, write the installation record, --trust-unsigned dev override; (b) the loader: scan installed plugins at boot, dynamic-import dist/index.mjs, reuse plugin-loader.ts dependency ordering, tier dispatch point (T0 only), disable/uninstall through the package disable-state store; (c) Stripe sample as the e2e acceptance; (d) delete the "NOT RE-VERIFIED AT UNPACK" / "no os plugin install" self-descriptions the code carries today. Lanes: cli, server. ⛔ M1 is first-party / self-signed only and is not listed in any marketplace surface.
  • M2 — cloud fetch + enforcement (Blocked-by: M1). (a) cloud GET …/artifact serves the .osplugin blob with publisher + platform signatures; os plugin install <id>@<version> walks resolve → compat → consent → download; (b) per-plugin SecurePluginContext scoped to the granted set for services and hooks (PluginContext wrap at §3.5 step 7), delete granted-permissions-not-enforced.pin.test.ts and the PluginDisclosure.tsx:148 sentence in the same PR. Lanes: cloud, server.
  • M3 — third-party tier (Blocked-by: M2). T1 dispatch to QuickJS for runtime:'sandbox'; publish-side secret / vulnerability scan; secret-refs to the settings/KV store; the fs / network enforce-or-remove decision; key rotation, T2 RPC and dependency-conflict answers. Lanes: cloud, server, spec.

Risks the children inherit

  • M1's T0 is "author code in the host process": hard-limited to first-party / self-signed; written into v2 as a ⛔.
  • Every landed step deletes its matching "NOT ENFORCED" / "not verified" self-description and pin; leaving one makes the gates red and the record false.
  • Scope: UI plugins (ADR-0026, Deferred) and the declarative connector sub-path (ADR-0023/0024) stay out; .osplugin serves plugins that need npm dependencies or host APIs.

Done-when

  • M0 v2 merged; ADR-0025's status line reads Accepted (narrowed) with the M1–M3 plan.
  • M1: a self-signed .osplugin installs locally, loads at boot, runs the Stripe sample end to end, disables cleanly.
  • M2: the same plugin installs from the cloud registry and a denied service/hook call is refused and logged.
  • M3: an unverified publisher's runtime:'sandbox' plugin runs under QuickJS; the pin tests and self-descriptions listed above are gone.

Related

ADR-0025 (record), ADR-0019 §Out-of-scope (the "load-bearing wall"), ADR-0016 §9 (local-first), ADR-0049 (enforce-or-remove), #26553 (ADR batch 4 ruling), #17757 (closed design card), #13457 (F4 registration), cloud plugins/connector-stripe.

Activity

  1. added
    enhancementNew feature or request
    priority:p1High: required for production / M2
    pm:epicReserved by a dedicated epic PM, parent or sub-issue; other PMs never take it; never with pm:queue
    on Oct 28, 2026
  2. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Children · director seat · 2026-10-28T06:02Z

    Ruling of record: objectstack#26553 comment 6461953053 (ADR-0025 → A narrowed + this programme). ⛔ Not a claim.

  3. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    M0 landed · director seat · 2026-10-28T06:58Z

    PR #27118 (ADR-0025 v2) merged 2026-10-28T06:56Z (main 07d18e6927); #27110 closed completed. The record now carries the Q1–Q8 decisions in §4, the §3.5 M1/M2 split, the §3.6/§3.7 phasing and the §7 answers, so the M1–M3 children can be written against it. The director seat files M1 (local T0 install loop, cli + server), M2 (cloud fetch + enforcement, cloud + server) and M3 (third-party tier) next, each Blocked-by: its predecessor and Part of #27108; triage grades and sets each lane. ⛔ Not a claim.

  4. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Children filed · director seat · 2026-10-28T07:01Z

    Triage grades each child, sets its lane(s) and may split by lane. ⛔ Not a claim.

  5. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling · four additions to this epic · 2026-10-28T14:31Z

    Maintainer rulings (objectstack session session_01J8VVAjRKqdoEYQ4hXxtBuf, 2026-10-28, verbatim, not translated), reached in the session thread after the seat put the four open items to the maintainer directly: 「插件应该支持从应用市场安装才是合理的」 · 「代码插件要到下次启动才加载,这个问题也要解决」 · 「好,把热加载和依赖策略并进清单」 · on the four-item table below, 「同意」. ⛔ Not a claim; the epic PM integrates these into the milestone chain. The children M4 / M5 / the SDK-contract card are filed by this seat on these rulings and name this comment.

    # Decision Effect on the record
    1 Plugins get an administrator shelf in the marketplace. The consumer shelf stays Apps-only (ADR-0019 D1 unchanged); a second shelf, visible and usable only to holders of manage_platform_settings, lists artifact_kind: 'plugin' versions and installs them through the M2 path. ADR-0019 D2 is amended ("plugins are never in the consumer marketplace" → "plugins are listed on the administrator shelf, never on the consumer shelf"); this epic's 「M1 is listed on no marketplace surface」 stands for M1 and is lifted by M4. Tier H: the ADR line is drafted by the seat that drafts the others and landed by the maintainer.
    2 Hot attach / detach without restart is a goal of this epic, sequenced after M2 as M5, not injected into M1/M2. Restart stays the rule for three plugin classes: plugins that extend sign-in (the better-auth instance is built at boot), database drivers, and plugins replacing a kernel core service; they declare it and the shelf shows "restart required". Reverses Q8's 「hot reload retired with #12587 → boot activation」 for the general case; ADR-0025 v2 §3.5 step 7 ("On next boot") gains the M5 arm. Tier H as above.
    3 A public plugin SDK contract and an external plugin repository are committed, between M2 and M3: the three seams external plugins need (messaging channel provider, identity-object writes, a public PluginContext subset), a conformance testkit, the peer-dependency build check; objectstack-ai/plugins as the first-party home, the narrowed DingTalk plugin (per-user push channel + user mapping, sign-in via an identity broker) as its first resident. New child card; M3's third-party tier builds on it.
    4 Shared-environment postures never load third-party code. group / isolated environments (the cloud console's own) install no plugin from the shelf; only an environment with a dedicated runtime does. Same shape as the credential ruling on #27230 (S1). A ⛔ line for M3 and M4; no card.

    Dependency policy (no new decision; acceptance additions). Q6 stands (bundled only; native addons refused in M1). M3's publish-side scan owes three more checks, to be written into the M3 card when it is filed: an SBOM emitted by os plugin build and stored on the version; a licence allowlist refusing copyleft dependencies at publish (bundling is redistribution); an artifact size cap. Plugins are also told, in the author handbook, to prefer the platform's fetch / crypto over vendor SDKs.

    Chain after these: M0 ✅ → M1 #27123 (in flight) → M2 #27124 → SDK contract (new) → M3 (to file) → M4 shelf (new) → M5 hot attach (new).


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Cross-reference · sibling epic #27293 · 2026-10-28T14:52Z

    #27293 「Marketplace and connector auto-shelf」 is the second epic the maintainer agreed to today (「是否建议立专题卡统一处理。」 → 「同意」, session session_01J8VVAjRKqdoEYQ4hXxtBuf). Division of labour: this epic is code plugins (install, load, enforce, shelf, hot attach); #27293 is zero-code integrations (openapi/mcp hardening #26709, outbound OAuth #26702, request signing #26710, administrator credentials #27237 / #27238, author recipes #27223 and handbook #27240, the first shelf of connector apps). Two touch points, each named on both epics: code-bearing integrations (DingTalk per-user push, WeCom, Feishu) go through #27287's SDK contract here, not there; M4's shelf and E4 share the listing packageType enum change — whichever lands first adds both values. ⛔ Not a claim.


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling · #27108 after the bridges decision · 2026-10-28T16:22Z

    Maintainer rulings (cloud project thread, 2026-10-28, verbatim, not translated): on the decision card 「插件专题 #27108 在中转方案之后怎么处理?」 the maintainer chose 「M1 做完,其余冻结」 (16:18Z), after choosing bridges over vendor plugins for DingTalk / WeCom / Feishu (16:10Z–16:16Z thread, epic #27314). ⛔ Not a claim.

    # Decision Effect
    1 M1 #27123 finishes as ruled (B′, 6467932167). Unchanged; slice 1 in flight.
    2 Frozen: M2 #27124, M3 #27125, M4 #27284, M5 #27285, SDK contract #27287. Unfreeze when a paying customer asks for third-party code distribution. Each child gets a comment naming this ruling; none is dispatched.
    3 Ruling 6471792611 item 3 is withdrawn: plugin-dingtalk is not the first resident of an external plugins repository. DingTalk, WeCom and Feishu go through bridges (objectstack-ai/bridges, epic #27314). #27287 frozen; its DingTalk acceptance moves to the bridges epic.
    4 Hosted ObjectOS Cloud loads no code plugins (one hosted process serves many environments' kernels — not a dedicated runtime; item 4's rule applied to hosted). Follows from 2: with M2 and M4 frozen there is no fetch or shelf path to a hosted environment; the thread's open card on this question was closed as this option on that basis, not by a separate tap. Code plugins remain for self-hosted / Enterprise dedicated runtimes through M1.

    M1 note for slice 2: its acceptance names cloud plugins/connector-stripe, which is closed-source in the private cloud repository (README.md:8, package.json "license": "UNLICENSED"), so objectstack CI cannot build it. Use an open fixture plugin for the objectstack e2e; the Stripe e2e runs in cloud CI after the pin carries M1.

    M1 note for slice 2 (Docker-deployed Enterprise): the EE image declares VOLUME ["/data"] with OS_HOME=/data (cloud apps/objectos-ee/Dockerfile:565-585), so a plugin installed at image build time into <OS_HOME>/plugins/ is hidden once the volume mounts. Customers who bake plugins into a derived image (FROM objectos-ee:x, Kubernetes, immutable deploys) need the loader to also scan a read-only plugin directory outside OS_HOME (e.g. OS_PLUGINS_DIR), with the same signature re-verification at boot. Runtime install into the /data volume (docker compose exec app os plugin install …, then restart) works as designed.


    Generated by Claude Code

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

    enhancementNew feature or requestpm:epicReserved by a dedicated epic PM, parent or sub-issue; other PMs never take it; never with pm:queuepriority:p1High: required for production / M2

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions