Skip to content

feat(#581): publish/publish_rollback native tools behind sandbox_publish_enabled (P2) - #786

Merged
Weegy merged 3 commits into
mainfrom
feat/581-publish-p2-tools
Aug 20, 2026
Merged

feat(#581): publish/publish_rollback native tools behind sandbox_publish_enabled (P2)#786
Weegy merged 3 commits into
mainfrom
feat/581-publish-p2-tools

Conversation

@Weegy

@Weegy Weegy commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Phase P2 of issue #581 (publish primitive), stacked on #785 (P1: version store, Docker runtime, origin-isolating gateway). Base branch: feat/581-publish (not main).

Wires the P1 library (@omadia/publish) into two agent-callable native tools, publish and publish_rollback, both behind a new operator flag sandbox_publish_enabled — following the #576 P2 execute-tool seam in harness-orchestrator/src/plugin.ts exactly (same file, same additive block shape). No route, no UI, no src/index.ts/agentBuilder.ts change — that's #778.

What's in this PR

  • publish tool (tools/publishTool.ts) — input {appId, name, dir, entrypoint}. Provisions the calling turn's own scope sandbox (same posture as execute: "publish always runs against the turn-scope sandbox" per the plan) and calls @omadia/publish's publish(). Before touching anything, runs defaultCommandPolicy() against a synthetic publish <appId> pseudo-command through the exact same decideCommand/recordCommandPolicyOutcome machinery execute uses. No default org-floor rule matches a pseudo-command like this, so it's inert today — wired for an operator who wants to gate publishing with a real policy rule, satisfying the plan's conditional "require_approval semantics falls Command-Policy Deploy-Kommandos betrifft" without inventing a bespoke policy surface.
  • publish_rollback tool (tools/publishRollbackTool.ts) — input {appId, version}. Same synthetic-command policy check (rollback <appId>), then calls rollbackTo(). That function doesn't even accept a PublishRuntime argument — this handler is structurally unable to trigger a rebuild no matter what.
  • plugin.ts wiring — both tools registered behind sandbox_publish_enabled (independent of sandbox_execute_enabled; an operator can enable one without the other), disposed on deactivate via the same try/dispose-array-loop every other native-tool block in this file uses. PublishStore picks PostgresPublishStore when the shared graph pool is configured, else falls back to InMemoryPublishStore — the tool works out of the box with zero extra Postgres setup (process-lifetime durability only), same posture InMemorySandboxRegistry documents for Durable per-scope sandbox with an execute tool #576.

Testing

  • 15 new tests (test/sandbox/publishTool.test.ts, publishRollbackTool.test.ts), same shape as executeTool.test.ts: every refused path (deny / require_approval / policy-resolve-failure / malformed input) asserts NEITHER the sandbox backend NOR the runtime NOR the store's pointer was touched, before the permitted-path tests assert the plumbing actually runs end to end (real publish()/rollbackTo() against an InMemoryPublishStore).
  • Combined regression sweep (test/publish/** + test/sandbox/** + buildOrchestrator/orchestratorDispatcher/orchestratorRegistry): 133/133 passing, exit 0, after a full npm run build.
  • Mutation-checked (rebuilt dist/ between probes and after restoring): disabling the rollback tool's deny-decision branch failed exactly the policy-check suite (5 pass / 1 fail); bypassing the publish tool's zod input validation failed exactly the malformed-input test (8 pass / 1 fail). Both restored, full suite re-verified green after rebuild.
  • node scripts/check-core-decoupling.mjs: held at 3296 (unchanged).
  • npm run typecheck:test: held at 406 known errors (unchanged).
  • npx eslint packages/harness-orchestrator/src/plugin.ts packages/harness-orchestrator/src/tools/publishTool.ts packages/harness-orchestrator/src/tools/publishRollbackTool.ts: clean, no output.
  • Full npm run build and npm run typecheck -w @omadia/orchestrator: clean.

Blast radius

  • plugin.ts: two new import blocks + one new flag-gated registration block (mirrors the execute block immediately above it) + one new disposer loop in deactivate. Nothing else in the file touched — no existing tool's registration, spec, or behavior changed.
  • Two new tool files, two new test files, harness-orchestrator/package.json gained a peerDependency on @omadia/publish.
  • Nothing runs, nothing is registered, for any deployment that has not explicitly set sandbox_publish_enabled=true — same honest-inert convention as sandbox_execute_enabled.
  • No migration in this PR (0045 shipped in feat(#581): publish primitive P1 — version store + Docker runtime + origin-isolating gateway #785).

Open questions for Marcel

Same three as #785 (runtime image scope, stale-version cleanup, apps-host/reverse-proxy config), plus:

  1. Is the synthetic pseudo-command policy check (publish <appId> / rollback <appId>) the right shape, or should publish/rollback get their own dedicated policy hook distinct from decideCommand's shell-command-oriented matching? I reused the existing machinery deliberately (no new policy surface), but the string isn't a real shell command — flagging in case that reuse reads as a stretch rather than a fit.

Stack

Base: feat/581-publish (#785, P1). This PR does not touch main directly. P3 (GrantStore-based sharing/rollback rights) will stack on top of this per the phase-4c plan.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Weegy added 2 commits August 20, 2026 16:49
…rigin-isolating gateway

Issue #581 (publish primitive: agent-built internal web apps with
immutable versions and rollback), phase P1 per the phase-4c plan: version
store, Docker-backed runtime, and the origin-isolation gateway. No native
tool and no route — that is P2, wired through harness-orchestrator's
plugin.ts sandbox_execute_enabled seam per the plan.

New package @omadia/publish (middleware/packages/harness-publish):

- publishStore.ts / postgresPublishStore.ts: PublishStore has no
  update/delete method by design — a republish can never mutate an
  existing version's row, only create a new one. Postgres allocates
  version numbers via SELECT...FOR UPDATE on a per-app counter row inside
  the same transaction as the version insert, so concurrent publishes to
  one app serialize rather than collide; the (app_id, version) primary key
  is the second, independent backstop. rollbackTo (in publish.ts) only
  ever calls store.setPointer — its signature does not even take a
  PublishRuntime, so it cannot trigger a new build/deploy.
- treeCollector.ts: reads the published directory only through
  Sandbox.list()/read() (already traversal-clamped by @omadia/sandbox),
  recursing on the paths list() itself returns — no raw filesystem path
  from agent input anywhere in this module.
- dockerPublishRuntime.ts: one immutable container per (appId, version)
  (deploy() is a no-op if that version's container already exists), one
  Docker volume per appId mounted at  in every version's
  container. That volume reuse is the entire mechanism behind the
  durability contract: a file outside  lives in the version's own
  container and is gone the moment a new version's container replaces it;
  a file inside  is on the shared volume and survives every
  redeploy. Verified against a real Docker daemon (SANDBOX_DOCKER_TEST=1
  gate, #576's pattern) in dockerPublishRuntime.test.ts, including an
  end-to-end  persistence proof and serving a real Node
  entrypoint over HTTP.
- publishGateway.ts: the origin boundary published apps run behind.
  Rejects any request whose Host does not end in a dedicated apps
  suffix (including an exact match on the admin host) before ever
  resolving an app backend; strips Cookie/Authorization before
  proxying; strips any Set-Cookie the app tries to scope with an
  explicit Domain=. Tested with two plain http.Server instances (no
  Docker, no browser) proving a forwarded admin session cookie never
  reaches the app backend and a domain-scoped Set-Cookie from the app
  never reaches the client.

Migration 0045_publish_versions.sql: publish_versions (app_id, version)
PK, publish_apps holding the next-version counter and current-version
pointer with a composite FK back to publish_versions so a pointer can
only ever reference a version that genuinely exists.

Testing: 32 tests in middleware/test/publish/ (stub-tier, always-on) all
green; +10 more under SANDBOX_DOCKER_TEST=1 including the real-Docker
 round trip, also green. postgresPublishStore.pg.test.ts skips
cleanly with no test Postgres configured (same convention as
postgresSandboxRegistry.pg.test.ts). Combined with test/sandbox/**:
89/89 passing. Mutation-checked: reverting the gateway's Set-Cookie
Domain= filter, DockerPublishRuntime's containerExists no-op guard, and
treeCollector's maxFiles cap each independently fails exactly the test
meant to catch it, confirmed with a dist/ rebuild between runs.

check-core-decoupling.mjs: held at 3296 (unchanged). check-test-typecheck
ratchet: held at 406 known errors (unchanged) — the new @ts-expect-error
compile-time proof in publishStore.test.ts type-checks clean.

Not in this PR (later phases per the plan): the publish/rollback native
tools behind an operator flag, GrantStore-based sharing, and the optional
admin version-list view.
…ish_enabled (P2)

Stacked on feat/581-publish (P1: version store + Docker runtime +
origin-isolating gateway, PR #785). Wires the #581 P1 library into two
agent-callable native tools, following the #576 P2 execute-tool seam in
harness-orchestrator/src/plugin.ts exactly (same file, same additive
block shape, own flag) — no src/index.ts or agentBuilder.ts change.

- publish (tools/publishTool.ts): reads {appId, name, dir, entrypoint},
  provisions the CALLING TURN's scope sandbox (same
  resolveScopeKey posture as execute — 'publish läuft IMMER über den
  Sandbox des Turn-Scopes' per the plan), and calls @omadia/publish's
  publish(). Runs defaultCommandPolicy() against a synthetic
  'publish <appId>' pseudo-command through the same decideCommand/
  recordCommandPolicyOutcome machinery execute uses, before ever touching
  a sandbox or the version store — inert under the shipped org floor
  (no default rule matches a pseudo-command), wired for an operator who
  adds one. This is the conditional 'require_approval semantics IF the
  command policy concerns deploy commands' the phase plan asks for,
  reusing existing infrastructure rather than inventing a bespoke
  publish-only policy surface.
- publish_rollback (tools/publishRollbackTool.ts): reads {appId,
  version}, runs the same synthetic-command policy check against
  'rollback <appId>', then calls @omadia/publish's rollbackTo(). That
  function does not even accept a PublishRuntime, so this handler is
  structurally unable to trigger a new build/deploy.

plugin.ts wiring: both tools registered behind sandbox_publish_enabled
(independent of sandbox_execute_enabled — an operator can enable one
without the other), disposed on deactivate via the same
try/dispose-array-loop pattern as every other native-tool block in this
file. PublishStore uses the shared graph pool (PostgresPublishStore) when
configured, else falls back to InMemoryPublishStore (process-lifetime
durability) — the tool is usable with zero extra Postgres setup, same
posture InMemorySandboxRegistry documents for #576.

Testing: 15 new tests (test/sandbox/publishTool.test.ts,
publishRollbackTool.test.ts) mirroring executeTool.test.ts's shape —
every refused path (deny/require_approval/policy-resolve-failure/
malformed input) asserted to touch NEITHER the sandbox backend NOR the
runtime NOR the store's pointer, before the permitted-path tests assert
the plumbing actually runs. Combined regression sweep (test/publish/** +
test/sandbox/** + buildOrchestrator/orchestratorDispatcher/
orchestratorRegistry): 133/133 passing after a full rebuild.

Mutation-checked (dist/ rebuilt between probes and after restore):
disabling the rollback tool's deny-decision branch failed exactly the
policy-check suite (5 pass / 1 fail); bypassing the publish tool's zod
input validation failed exactly the malformed-input test (8 pass / 1
fail). check-core-decoupling.mjs held at 3296; test-typecheck ratchet
held at 406 — both unchanged.

Blast radius: additive-only within harness-orchestrator/src/plugin.ts
(two new import blocks + one new flag-gated registration block + one new
disposer loop, inserted next to the existing execute-tool block, nothing
else in the file touched); two new tool files; two new test files;
harness-orchestrator/package.json gained a peerDependency on
@omadia/publish. No route, no UI, no existing tool's behavior changed.
Nothing runs for any deployment that has not set
sandbox_publish_enabled=true.
@Weegy
Weegy merged commit 66e7579 into main Aug 20, 2026
9 checks passed
@Weegy
Weegy deleted the feat/581-publish-p2-tools branch August 20, 2026 15:52
Weegy added a commit that referenced this pull request Aug 20, 2026
Conflicts in plugin.ts and publishTool.ts: main carries P2's squash (#786),
this branch carries P2 plus P3's deliberate evolution (grant-checked handler
registration; the additive resolveScopeKey export). Verified via
git log <merge-base>..origin/main that only the #786 squash touched either
file since this branch diverged, so the branch side is the correct
resolution. Full-tree conflict-marker sweep: clean.
Weegy added a commit that referenced this pull request Aug 20, 2026
…/rollback (P3) (#790)

* feat(#581): publish primitive P1 — version store + Docker runtime + origin-isolating gateway

Issue #581 (publish primitive: agent-built internal web apps with
immutable versions and rollback), phase P1 per the phase-4c plan: version
store, Docker-backed runtime, and the origin-isolation gateway. No native
tool and no route — that is P2, wired through harness-orchestrator's
plugin.ts sandbox_execute_enabled seam per the plan.

New package @omadia/publish (middleware/packages/harness-publish):

- publishStore.ts / postgresPublishStore.ts: PublishStore has no
  update/delete method by design — a republish can never mutate an
  existing version's row, only create a new one. Postgres allocates
  version numbers via SELECT...FOR UPDATE on a per-app counter row inside
  the same transaction as the version insert, so concurrent publishes to
  one app serialize rather than collide; the (app_id, version) primary key
  is the second, independent backstop. rollbackTo (in publish.ts) only
  ever calls store.setPointer — its signature does not even take a
  PublishRuntime, so it cannot trigger a new build/deploy.
- treeCollector.ts: reads the published directory only through
  Sandbox.list()/read() (already traversal-clamped by @omadia/sandbox),
  recursing on the paths list() itself returns — no raw filesystem path
  from agent input anywhere in this module.
- dockerPublishRuntime.ts: one immutable container per (appId, version)
  (deploy() is a no-op if that version's container already exists), one
  Docker volume per appId mounted at  in every version's
  container. That volume reuse is the entire mechanism behind the
  durability contract: a file outside  lives in the version's own
  container and is gone the moment a new version's container replaces it;
  a file inside  is on the shared volume and survives every
  redeploy. Verified against a real Docker daemon (SANDBOX_DOCKER_TEST=1
  gate, #576's pattern) in dockerPublishRuntime.test.ts, including an
  end-to-end  persistence proof and serving a real Node
  entrypoint over HTTP.
- publishGateway.ts: the origin boundary published apps run behind.
  Rejects any request whose Host does not end in a dedicated apps
  suffix (including an exact match on the admin host) before ever
  resolving an app backend; strips Cookie/Authorization before
  proxying; strips any Set-Cookie the app tries to scope with an
  explicit Domain=. Tested with two plain http.Server instances (no
  Docker, no browser) proving a forwarded admin session cookie never
  reaches the app backend and a domain-scoped Set-Cookie from the app
  never reaches the client.

Migration 0045_publish_versions.sql: publish_versions (app_id, version)
PK, publish_apps holding the next-version counter and current-version
pointer with a composite FK back to publish_versions so a pointer can
only ever reference a version that genuinely exists.

Testing: 32 tests in middleware/test/publish/ (stub-tier, always-on) all
green; +10 more under SANDBOX_DOCKER_TEST=1 including the real-Docker
 round trip, also green. postgresPublishStore.pg.test.ts skips
cleanly with no test Postgres configured (same convention as
postgresSandboxRegistry.pg.test.ts). Combined with test/sandbox/**:
89/89 passing. Mutation-checked: reverting the gateway's Set-Cookie
Domain= filter, DockerPublishRuntime's containerExists no-op guard, and
treeCollector's maxFiles cap each independently fails exactly the test
meant to catch it, confirmed with a dist/ rebuild between runs.

check-core-decoupling.mjs: held at 3296 (unchanged). check-test-typecheck
ratchet: held at 406 known errors (unchanged) — the new @ts-expect-error
compile-time proof in publishStore.test.ts type-checks clean.

Not in this PR (later phases per the plan): the publish/rollback native
tools behind an operator flag, GrantStore-based sharing, and the optional
admin version-list view.

* feat(#581): publish/publish_rollback native tools behind sandbox_publish_enabled (P2)

Stacked on feat/581-publish (P1: version store + Docker runtime +
origin-isolating gateway, PR #785). Wires the #581 P1 library into two
agent-callable native tools, following the #576 P2 execute-tool seam in
harness-orchestrator/src/plugin.ts exactly (same file, same additive
block shape, own flag) — no src/index.ts or agentBuilder.ts change.

- publish (tools/publishTool.ts): reads {appId, name, dir, entrypoint},
  provisions the CALLING TURN's scope sandbox (same
  resolveScopeKey posture as execute — 'publish läuft IMMER über den
  Sandbox des Turn-Scopes' per the plan), and calls @omadia/publish's
  publish(). Runs defaultCommandPolicy() against a synthetic
  'publish <appId>' pseudo-command through the same decideCommand/
  recordCommandPolicyOutcome machinery execute uses, before ever touching
  a sandbox or the version store — inert under the shipped org floor
  (no default rule matches a pseudo-command), wired for an operator who
  adds one. This is the conditional 'require_approval semantics IF the
  command policy concerns deploy commands' the phase plan asks for,
  reusing existing infrastructure rather than inventing a bespoke
  publish-only policy surface.
- publish_rollback (tools/publishRollbackTool.ts): reads {appId,
  version}, runs the same synthetic-command policy check against
  'rollback <appId>', then calls @omadia/publish's rollbackTo(). That
  function does not even accept a PublishRuntime, so this handler is
  structurally unable to trigger a new build/deploy.

plugin.ts wiring: both tools registered behind sandbox_publish_enabled
(independent of sandbox_execute_enabled — an operator can enable one
without the other), disposed on deactivate via the same
try/dispose-array-loop pattern as every other native-tool block in this
file. PublishStore uses the shared graph pool (PostgresPublishStore) when
configured, else falls back to InMemoryPublishStore (process-lifetime
durability) — the tool is usable with zero extra Postgres setup, same
posture InMemorySandboxRegistry documents for #576.

Testing: 15 new tests (test/sandbox/publishTool.test.ts,
publishRollbackTool.test.ts) mirroring executeTool.test.ts's shape —
every refused path (deny/require_approval/policy-resolve-failure/
malformed input) asserted to touch NEITHER the sandbox backend NOR the
runtime NOR the store's pointer, before the permitted-path tests assert
the plumbing actually runs. Combined regression sweep (test/publish/** +
test/sandbox/** + buildOrchestrator/orchestratorDispatcher/
orchestratorRegistry): 133/133 passing after a full rebuild.

Mutation-checked (dist/ rebuilt between probes and after restore):
disabling the rollback tool's deny-decision branch failed exactly the
policy-check suite (5 pass / 1 fail); bypassing the publish tool's zod
input validation failed exactly the malformed-input test (8 pass / 1
fail). check-core-decoupling.mjs held at 3296; test-typecheck ratchet
held at 406 — both unchanged.

Blast radius: additive-only within harness-orchestrator/src/plugin.ts
(two new import blocks + one new flag-gated registration block + one new
disposer loop, inserted next to the existing execute-tool block, nothing
else in the file touched); two new tool files; two new test files;
harness-orchestrator/package.json gained a peerDependency on
@omadia/publish. No route, no UI, no existing tool's behavior changed.
Nothing runs for any deployment that has not set
sandbox_publish_enabled=true.

* feat(#581): publish sharing via GrantStore — read=use, write=redeploy/rollback (P3)

Stacked on feat/581-publish-p2-tools (P2: publish/publish_rollback native
tools, PR #786). Wires the #576/#575 GrantStore sharing model into P2's
tool handlers, matching the issue text: "Sharing per scope grant (read =
use, write = redeploy/rollback)".

- tools/publishAccess.ts (new): checkPublishAccess() — the core decision.
  Ownership is scope-key equality against an app's version-1
  sourceScopeKey (already recorded by P1, nothing new to store): the
  owner needs NO grant lookup at all, so sharing cannot lock out the
  owner by construction, not by a special-cased bypass. A brand-new
  appId with no version yet is allowed unconditionally — the first
  publish call establishes ownership. For everyone else, resolves a
  Principal from the caller's session scope (only personal:<userId>
  scopes map to one — the same omadia-user-id space
  resolveOrCreateChannelIdentity uses per #575/#333) and checks
  resolveCapabilities() for a publish:read:<appId> / publish:write:<appId>
  capability, denials winning over grants (same rule audienceFloor.ts/
  skillSharing.ts apply). Fails CLOSED on every unresolvable case
  (non-personal scope, partial role lookup, throwing store) — the
  opposite failure direction from skillSharing.ts's deliberately fail-open
  default, because here an unresolved lookup reading as "granted" would
  let an unrelated scope redeploy or roll back someone else's app.
  Also ships createGrantCheckedResolveTarget() — the read-gated
  counterpart for PublishGateway.resolveTarget — as a tested, ready
  primitive; NOT wired into a live server (the anonymous, origin-isolated
  P1 gateway strips caller identity by design, so an authenticated caller
  has to come from wherever #778 builds one).
- tools/publishGrantedTools.ts (new): createGrantCheckedPublishHandler /
  createGrantCheckedPublishRollbackHandler wrap P2's
  createPublishHandler/createPublishRollbackHandler with a write-capability
  check before delegating. Neither P2 file's body is modified — one
  additive `export { resolveScopeKey }` line at the end of publishTool.ts
  is the only change there, so the wrapper resolves the SAME scope key the
  inner handler provisions its sandbox under instead of a third
  copy-pasted implementation. grants.ts itself (#575) is untouched —
  consumed only via its exported GrantStore/resolveCapabilities contract,
  same posture skillSharing.ts documents.
- plugin.ts: when audienceGrants (the GrantStore #575 already publishes as
  a service) is configured, both tools register the grant-checked
  handlers; otherwise the raw P2 handlers run exactly as before — no
  behavior change for a deployment that has not opted into grants.
  RoleSourceRegistryImpl here is a fresh, empty registry (documented as a
  known v1 gap: no role-source registry is published as a shared service
  ANYWHERE in this codebase yet, Orchestrator builds its own private one
  the same way) — direct grants work fully, role grants resolve to
  nothing until that changes.

Testing: 26 new tests. publishAccess.test.ts (17) proves both directions
explicitly for every rule — owner needs no grant (write AND read), a
fresh appId is open, a non-owner with no grant is denied (write and
read), a write grant does not imply read and vice versa, a grant for a
DIFFERENT appId does not leak, a direct denial beats a direct grant, a
role grant covers any holder, a non-personal scope is denied even with a
matching grant on file, and a throwing role source fails closed even
though a direct grant alone would have sufficed.
publishGrantedTools.test.ts (9) exercises the wrappers end-to-end through
the real native-tool handler shape: the owner republishes/rolls back its
own app with ZERO grants configured (sharing cannot lock out the owner);
a denied call returns an explicit refusal string naming the app and
touches neither the sandbox backend nor the runtime nor the store's
pointer (never a silent no-op); a write grant lets a non-owner both
publish and roll back; a read grant is proven NOT sufficient for
rollback.

Combined regression sweep (test/publish/** + test/sandbox/** +
buildOrchestrator/orchestratorDispatcher/orchestratorRegistry +
skillSharing + audienceFloor, after a full npm run build): 189/189
passing.

Mutation-checked (dist/ rebuilt between probes and after restore):
removing the denials-win check failed exactly the denial-direction
suite (16 pass / 1 fail); disabling ownership equality (treating every
caller as the owner) failed 6 of 6 affected suites (12 pass / 14 fail) —
the strongest possible signal these tests are load-bearing; bypassing
the publish wrapper's grant check entirely failed exactly its own
suite (8 pass / 1 fail). All three restored, full suite re-verified
green after rebuild.

check-core-decoupling.mjs: held at 3296 (unchanged). test-typecheck
ratchet: held at 406 (unchanged) — required switching the two new test
files' GrantStore/RoleSourceRegistry/InMemoryGrantStore imports from a
relative packages/harness-channel-sdk/src path to the '@omadia/channel-sdk'
package specifier, matching skillSharing.test.ts's existing convention:
importing a class with private fields (RoleSourceRegistry,
InMemoryGrantStore) from its src/ path while the code under test receives
it via the package's dist/ declaration makes TypeScript see two nominally
distinct types even though they're the same source.

Blast radius: two new files in harness-orchestrator/src/tools/, two new
test files. plugin.ts gains one new conditional branch inside the
existing sandbox_publish_enabled block (picks the grant-checked handler
when audienceGrants is configured, the raw P2 handler otherwise) — no
other block in the file touched. publishTool.ts gains exactly one
additive re-export line. grants.ts (#575), publishTool.ts's/
publishRollbackTool.ts's existing bodies, and every P1 file are
untouched. No route, no UI, no migration. Nothing changes for a
deployment that has not set sandbox_publish_enabled=true, and — within
that — nothing changes for one that has not also wired a GrantStore.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant