@@ -386,14 +386,20 @@ function requireReadCapability(deps: DomainHandlerDeps, context: HttpProtocolCon
386386 * (ADR-0070 D2). Revert asks it only after the protocol's stored-row answer, so
387387 * a `sys_metadata` row bound to the package keeps its own answer.
388388 *
389- * [#14451] The remedy it names is ADR-0005 org overlay, and it used to name
390- * `POST /packages/:id/duplicate` instead. That sentence sent a caller holding a
391- * read-only package at a route which — for exactly the packages this refusal
392- * fires on — cannot help: duplicate clones a base's `sys_metadata` rows, a code
393- * package owns none, and until {@link requireDuplicableSource} the trip ended
394- * in a 200 reporting `copiedCount: 0` (the #14451 measurement). A refusal that
395- * prescribes a dead end is worse than one that prescribes nothing, so the two
396- * doors now agree: overlay is how a code package is customised.
389+ * [#14451] It used to name `POST /packages/:id/duplicate` as the remedy. That
390+ * sentence sent a caller holding a read-only package at a route which — for
391+ * exactly the packages this refusal fires on — cannot help: duplicate clones a
392+ * base's `sys_metadata` rows, a code package owns none, and until {@link
393+ * requireDuplicableSource} the trip ended in a 200 reporting `copiedCount: 0`
394+ * (the #14451 measurement). A refusal that prescribes a dead end is worse than
395+ * one that prescribes nothing.
396+ *
397+ * [ADR-0131 D6] For the same reason it no longer names the ADR-0005
398+ * organization overlay, its remedy after that: the per-organization overlay
399+ * axis is retired, and a write naming an organization answers `403
400+ * NOT_OVERRIDABLE`. What a managed package provides is customised BESIDE it,
401+ * as environment-level state, and the two doors name that remedy in one
402+ * clause ({@link CUSTOMISE_BESIDE_REMEDY}).
397403 *
398404 * ⛔ Deliberately NOT caller-sensitive — there is no `isSystem` bypass, unlike
399405 * {@link requireManageMetadata}. Read-only is a property of the PACKAGE. Engine
@@ -521,6 +527,24 @@ function commitItemTypes(items: unknown): unknown[] {
521527 return Array . isArray ( list ) ? list . map ( ( item ) => ( item as { type ?: unknown } | null ) ?. type ) : [ ] ;
522528}
523529
530+ /**
531+ * [ADR-0131 D6] The customisation remedy both read-only refusals below name
532+ * ({@link requireWritablePackage}, {@link requireDuplicableSource}), in one
533+ * spelling. A managed package's items are sealed, and what a customer changes
534+ * lives beside them as environment-level state: a save under an item's own
535+ * name is an environment overlay on a type whose registry entry takes one
536+ * (`allowEnvironmentOverlay`) unless the package's lock refuses it — that save
537+ * is refused `ITEM_LOCKED`, naming the lock and its reason (ADR-0010 §3.3) —
538+ * and on every other type the save door's own refusal names the route that
539+ * type has: a flow's clone and switch, an action's switch, a permission set's
540+ * clone, else the source. The sentence names that door rather than a list of
541+ * types, so it cannot drift from the regime table the door reads.
542+ */
543+ const CUSTOMISE_BESIDE_REMEDY =
544+ 'customise what this package provides beside it, as environment-level state (ADR-0131 D6): save an item '
545+ + 'under its own name — that overlays it where the item\'s type takes an environment overlay and the package\'s '
546+ + 'lock does not refuse it, and is otherwise refused naming the route that type has, or the lock.' ;
547+
524548function requireWritablePackage (
525549 deps : DomainHandlerDeps ,
526550 engine : unknown ,
@@ -534,8 +558,8 @@ function requireWritablePackage(
534558 response : deps . error (
535559 `[writable_package_required] Cannot ${ action } package '${ id } ': it is read-only `
536560 + `(provided by code or an installed app). Packages the deployment ships are managed by `
537- + `the deployment, not over this API — ${ action } a package you own, or customise what `
538- + `this one provides in place, with an ADR-0005 org overlay.` ,
561+ + `the deployment, not over this API — ${ action } a package you own, or `
562+ + CUSTOMISE_BESIDE_REMEDY ,
539563 422 ,
540564 {
541565 code : 'WRITABLE_PACKAGE_REQUIRED' ,
@@ -591,9 +615,13 @@ function requireWritablePackage(
591615 * Making it clone a code package's items would extend D4 from bases to code
592616 * packages — a NEW decision, and one the ADR itself still lists as open
593617 * ("should customising a code item also fork it into a writable base?
594- * *Leaning: keep overlay for surgical tweaks*"). ADR-0005 overlay is the built,
595- * shipped answer for customising what a code package provides, so the honest
596- * behaviour of an unbuilt gesture is to say so.
618+ * *Leaning: keep overlay for surgical tweaks*"). Two built answers exist
619+ * instead (ADR-0131 D6): customising BESIDE the package, as environment-level
620+ * state ({@link CUSTOMISE_BESIDE_REMEDY}), and, for an editable copy of the
621+ * whole package, its `unmanaged` install, which the install door judges (the
622+ * package must permit it, and the copy cannot sit beside a managed install of
623+ * the same package). So the honest behaviour of an unbuilt gesture is to say
624+ * so and name both.
597625 *
598626 * ## Shape
599627 *
@@ -603,7 +631,8 @@ function requireWritablePackage(
603631 * a lie by implicature here: nothing is being written to the source, and its
604632 * remedy ("use a writable package") reads as *make the source writable*, which
605633 * is neither possible nor the point. `DUPLICATE_SOURCE_NOT_A_BASE` names the
606- * one thing the caller can act on — pick a base, or overlay instead.
634+ * one thing the caller can act on — pick a base, customise beside the package,
635+ * or install it unmanaged where it permits that.
607636 *
608637 * ⛔ Deliberately NOT a check on emptiness. A WRITABLE base holding no active
609638 * rows still duplicates to `copiedCount: 0`, and that answer stays exactly as
@@ -637,8 +666,9 @@ function requireDuplicableSource(
637666 + `(provided by code or an installed app), and duplicate clones a writable base's `
638667 + `stored metadata rows. A code package's metadata is delivered as code, so there are `
639668 + `no rows to clone and the copy would be empty (ADR-0070 D4 duplicates a BASE). `
640- + `Duplicate a base you own, or customise what this package provides in place, with an `
641- + `ADR-0005 org overlay.` ,
669+ + `Duplicate a base you own, or ${ CUSTOMISE_BESIDE_REMEDY } `
670+ + `For an editable copy of the whole package, install it unmanaged instead `
671+ + `(installMode 'unmanaged'), where its manifest's installModes permits that.` ,
642672 422 ,
643673 {
644674 code : 'DUPLICATE_SOURCE_NOT_A_BASE' ,
0 commit comments