Uh oh!
There was an error while loading. Please reload this page.
feat(app-shell): packaged actions on/off beside the packaged flows (ADR-0126 §8 item 2) - #6415
Merged
Merged
Conversation
…DR-0126 §8 item 2) Setup › Packaged automation gains a packaged ACTIONS section beside its flows one — the surface half of ADR-0126 §8 item 2 (amendment ruling 3). The engine, the ledger rows and both dispatch doors landed in objectstack PR 12348; objectui was deliberately out of that PR's scope, so this is the other half of one card. Per packaged action the section does exactly ONE thing: on/off for this scope. That is all the sys_metadata_activation ledger knows about an action. - State comes from the ledger's `metadata_type: 'action'` rows, read through the generic data API list the object itself sanctions for exactly this (`apiMethods: ['get', 'list']`). Absence of a row means active. - Discovery mirrors the runtime's own collectActionDeclarations: embedded `actions[]` from GET /meta/object plus standalone items from GET /meta/action, object-embedded winning an <object>:<action> key clash. - Flips invoke POST /actions/_activation/:object/:action with exactly the one key its body declares; `global` is the object segment for an object-less action, the spelling both dispatch doors take. NO clone for actions — ruling 3 charters the switch and nothing else, and the clone half stays pre-chartered. NO drift or ancestry surface (§9); the absence is pinned against a response that smuggles clonedFrom/baseVersion in, so it is enforced at the renderer and not merely by the wire. Refusals reach the operator verbatim, no softening and no retry loop: the §5 posture gate (403 PERMISSION_DENIED), the ambiguous-name refusal (409 RESOURCE_CONFLICT, naming the objects) and the no-ledger outage (503 SERVICE_UNAVAILABLE). A `hasMore` on the ledger read is a load failure rather than a rendered list — a dropped row reads as "active", so a partial ledger would show a switched-off action as armed. The flows section is unchanged in behaviour. Nine new packagedAutomation.* keys land in `en` and in all nine other packs as real translations; the page subtitle moved in all ten together. Claude-Session: https://claude.ai/code/session_01KWRU3s15AJz7PGW7a7wdCh Co-authored-by: Claude <noreply@anthropic.com>
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
os-support-ai
marked this pull request as ready for review
August 25, 2026 22:43
Uh oh!
There was an error while loading. Please reload this page.
9 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes#6412
Part of the objectstack-ai/objectstack#12150 program (ADR-0126, v17 line). This is the
surface half of ADR-0126 §8 item 2 / amendment ruling 3 — the engine, the ledger rows
and both dispatch doors landed in objectstack-ai/objectstack#12348 (merged
e5ce2ed0),which deliberately left objectui out of its scope. The maintainer's pull, verbatim and
untranslated:
What landed
Setup › Packaged automation (the §7.4 page, #6382) gains a
packaged actions section beside its flows one. Per packaged action it does exactly
one thing — on/off for this scope — because that is all the
sys_metadata_activationledger knows about an action.POST /automation/:name/togglePOST /actions/_activation/:object/:actionWhere each fact on a row comes from
There is no
_status-style read door for actions to mirror the flows half with — objectstack-ai/objectstack#12348shipped the flip only — and this PR does not invent one. It reads the two surfaces the
platform already sanctions, and joins them in a pure module (
packagedActions.ts), exactlythe shape
packagedFlows.tshas:collectActionDeclarations: object-embeddedactions[]fromGET /meta/objectplus standalone items fromGET /meta/action,with the object-embedded declaration winning an
<object>:<action>key clash. Listingonly the first source would leave an administrator with no off-switch for a packaged
standalone action — the defect
packagedFlows.tsnames for a filtered-out flow.metadata_type: 'action'rows via the generic data APIlist, which the object's own declaration sanctions for precisely this use:
apiMethods: ['get', 'list']— "Reads stay open so operability surfaces can answer'what is disabled here?'". Absence of a row means active, so a stock boot shows
everything armed.
isPackagedFlowItemrather than transcribing it a second time — itsclauses read the two keys the registry stamps on every metadata item and say nothing about
flows. What is new is which item is asked: an embedded action carries no provenance marks
of its own, so its owning object answers; a standalone item answers for itself. That is the
read-side twin of how the door derives the package it writes
(
action._packageId ?? obj._packageId).globalis the object segment for an object-less action — the spelling both dispatch doorstake, and the literal string the flip puts in the URL, so the object column shows what the
flip will actually address.
Refusals reach the operator verbatim
No client-side softening, no retry loop. All three shapes are reachable in tests and each is
transcribed character-for-character from the runtime's own message builders:
PERMISSION_DENIED— the §5 posture gate. Its remedy clause is the action oneand deliberately does not say "clone"; a client that reworded it would put back the
recommendation the gate carries a per-door sentence to avoid.
RESOURCE_CONFLICT— two objects declare this action name. Both rows stay listedand neither switch is pre-emptively disabled: the ledger addresses an action by name (§4),
and only the server can name the objects the flip would reach.
SERVICE_UNAVAILABLE— no ledger attached. An outage is not a verdict.One refusal is the section's own and points the same way: a
hasMoreon the ledger read istreated as a load failure rather than rendered. A dropped row reads as "active", so a
partial ledger would show a switched-off action as armed — the one direction this
section must not fail in.
Deliberate absences (ADR-0126 §9), pinned
⛔ No clone control. ⛔ No "customized" badge, diff-vs-base or base-moved notice. The
absence is asserted against a response that smuggles
clonedFrom/baseVersionin, so itis enforced at the renderer and not merely by the wire — a response field is the cheapest
place for ancestry to reappear.
Flows section
Behaviour unchanged. It gains a heading beside the new one, and the page subtitle now reads
"Flows and actions …" — that string moved in all ten packs together. The existing page test's
fake server answers the three new reads with empty lists so its assertions keep measuring the
flows half; leaving them unanswered would not have been neutral (its stub throws).
i18n
Nine new
packagedAutomation.*keys inenand real translations in all nine otherpacks — no inline
defaultValuestanding in for a pack entry (the objectui#3517 lesson).The two toggle-failure keys are artifact-neutral by wording and are reused rather than
duplicated into ten packs.
Verification
Everything below was run at
924d105, the branch head, with the gate's own verdict quotedrather than a shell
$?.vitest run packages/app-shell/src/views/setup/ packages/i18n/Test Files 60 passed (60)·Tests 968 passed (968)type-check(app-shell, i18n)Scope: 2 of 47 workspace projects· bothDonelint(app-shell, i18n)✖ 2680 problems (0 errors, 2680 warnings)— 0 errors; none attributable to the changed filescheck:i18n-keyscheck:i18n-driftcheck:control-bytes✅ OK (scanned 5308 tracked text file(s))check:eager-closure✅ chunkceilings, weighed against a freshturbo run build --filter=@object-ui/consolechangeset:check+check-changeset-presence✅ 16 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)Ablation — the verbatim-refusal relay was replaced with a client-side fallback
(
actionErrorDetail(json, fallback)→ a function returning onlyfallback). The mutationwas confirmed on disk by grepping for both the removed and the injected text (removed: 0
hits, injected: 1 hit) before the run, not by a diffstat. Predicted direction: red. Observed:
Tests 4 failed | 10 passed— the three refusal shapes plus the row-isolation case. Therestore leg was verified
cmp-identical to the pre-ablation file. No rebuild was neededand none is claimed: the test imports
./PackagedActionsSectionby relative path, so themodule under test resolves from source, never through a package
exports→dist/.Declared narrowing. The full
packages/app-shell/suite (536 test files) was started andhit the container's ~10-minute foreground cap (
exit 143). What ran instead is everyapp-shell test file that this diff can reach, derived by enumerating importers of the changed
modules and consumers of the one changed
envalue: onlysrc/index.tsandservices/builtinComponents.tsxreference the page outsideviews/setup/, neither has atest file, and
packagedAutomation.subtitlehas exactly one call site. The lint narrowing isrepo-level only — both changed packages were linted in full, and the population is
invariant because the flat config enables no type-aware linting (no
project/projectServiceinlanguageOptions), so this diff cannot move a verdict in an untouchedfile. The remaining farm is CI's run.
Generated by Claude Code