@aglyn/aglyn 1.0.0-beta.293 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +11 -11
- package/src/lib/app-utils/crm-kinds.d.ts +7 -1
- package/src/lib/app-utils/crm-kinds.js +11 -0
- package/src/lib/app-utils/crm-kinds.js.map +1 -1
- package/src/lib/app-utils/crm.d.ts +5 -1
- package/src/lib/app-utils/crm.js +51 -4
- package/src/lib/app-utils/crm.js.map +1 -1
- package/src/lib/app-utils/docs-help-sections.generated.js +4 -3
- package/src/lib/app-utils/docs-help-sections.generated.js.map +1 -1
- package/src/lib/app-utils/docs-help.generated.d.ts +1 -1
- package/src/lib/app-utils/docs-help.generated.js +2 -0
- package/src/lib/app-utils/docs-help.generated.js.map +1 -1
- package/src/lib/app-utils/docs-index.generated.js +24 -17
- package/src/lib/app-utils/docs-index.generated.js.map +1 -1
- package/src/lib/app-utils/screen-version-publish.d.ts +71 -0
- package/src/lib/app-utils/screen-version-publish.js +82 -0
- package/src/lib/app-utils/screen-version-publish.js.map +1 -0
- package/src/lib/app-utils/server.d.ts +1 -0
- package/src/lib/app-utils/server.js +1 -0
- package/src/lib/app-utils/server.js.map +1 -1
- package/src/lib/app-utils/version-draft.d.ts +61 -0
- package/src/lib/app-utils/version-draft.js +53 -0
- package/src/lib/app-utils/version-draft.js.map +1 -0
- package/src/lib/plugin-manager/plugin-jobs.d.ts +14 -0
- package/src/lib/plugin-manager/plugin-jobs.js +30 -7
- package/src/lib/plugin-manager/plugin-jobs.js.map +1 -1
- package/src/lib/plugin-manager/plugin-record-timeline.d.ts +5 -2
- package/src/lib/plugin-manager/plugin-record-timeline.js.map +1 -1
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import { type ScreenRouteNode } from './screen-route';
|
|
18
|
+
/**
|
|
19
|
+
* What Publish does to one version of a page, from outside the Besigner
|
|
20
|
+
* (AGL-3616): the preview of a version an AI job wrote leads with it.
|
|
21
|
+
*
|
|
22
|
+
* - `live` — the site already serves this version at `path`; nothing to do.
|
|
23
|
+
* - `replace` — the page is live with another version: Publish makes this
|
|
24
|
+
* one the version the site serves at `path`. `placeholder` where the page
|
|
25
|
+
* is the site's placeholder home (AGL-3478), which publishes through the
|
|
26
|
+
* route seam so it becomes the owner's.
|
|
27
|
+
* - `first` — the page is not on the site: Publish puts it at `path`, its
|
|
28
|
+
* own `slug` under its parents, with this version.
|
|
29
|
+
* - `blocked` — Publish cannot be done from here, and `reason` says why in
|
|
30
|
+
* the words the person reads. The Besigner is where each is fixed.
|
|
31
|
+
*
|
|
32
|
+
* The same refusals, in the same order, as the page's own Publish: no
|
|
33
|
+
* address, a parent with none, a reserved address, an address another page
|
|
34
|
+
* holds.
|
|
35
|
+
*/
|
|
36
|
+
export type ScreenVersionPublish = {
|
|
37
|
+
kind: 'live';
|
|
38
|
+
path: string;
|
|
39
|
+
} | {
|
|
40
|
+
kind: 'replace';
|
|
41
|
+
path: string;
|
|
42
|
+
placeholder: boolean;
|
|
43
|
+
} | {
|
|
44
|
+
kind: 'first';
|
|
45
|
+
slug: string;
|
|
46
|
+
path: string;
|
|
47
|
+
} | {
|
|
48
|
+
kind: 'blocked';
|
|
49
|
+
reason: string;
|
|
50
|
+
};
|
|
51
|
+
export declare const SCREEN_VERSION_PUBLISH_DELETED = "This page was deleted.";
|
|
52
|
+
export declare const SCREEN_VERSION_PUBLISH_NO_ADDRESS = "This page has no address yet. Give it one in the Besigner, then publish it.";
|
|
53
|
+
export declare const SCREEN_VERSION_PUBLISH_NO_PARENT_ADDRESS = "A page above this one is not published, so this page has no address yet. Publish that page first.";
|
|
54
|
+
export declare const screenVersionPublishTakenCopy: (path: string) => string;
|
|
55
|
+
export declare function screenVersionPublish(input: {
|
|
56
|
+
screenId: string;
|
|
57
|
+
versionId: string;
|
|
58
|
+
/** The page's document, `null` where it is gone. */
|
|
59
|
+
screen: {
|
|
60
|
+
versionId?: unknown;
|
|
61
|
+
slug?: unknown;
|
|
62
|
+
kind?: unknown;
|
|
63
|
+
parentId?: unknown;
|
|
64
|
+
deletedAt?: unknown;
|
|
65
|
+
} | null;
|
|
66
|
+
/** The site's routing map, `host.screens`. */
|
|
67
|
+
routingMap: Readonly<Record<string, string>> | null | undefined;
|
|
68
|
+
/** Every page of the site as path composition reads it (`toScreenRouteNode`). */
|
|
69
|
+
screensById: Readonly<Record<string, ScreenRouteNode | undefined>>;
|
|
70
|
+
defaultHomeScreenId?: string | null;
|
|
71
|
+
}): ScreenVersionPublish;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2026 Aglyn LLC
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/ import { blockingRouteOwner, composeScreenRoutePath, SCREEN_KIND_ERROR, SCREEN_KIND_GROUP, SCREEN_KIND_TEMPLATE, screenRoutePathToUrl, toScreenRouteNode } from "./screen-route.js";
|
|
18
|
+
import { normalizeScreenSlug, reservedScreenRouteMessage, reservedScreenRouteSegment } from "./screen-slug.js";
|
|
19
|
+
export const SCREEN_VERSION_PUBLISH_DELETED = 'This page was deleted.';
|
|
20
|
+
export const SCREEN_VERSION_PUBLISH_NO_ADDRESS = 'This page has no address yet. Give it one in the Besigner, then publish it.';
|
|
21
|
+
export const SCREEN_VERSION_PUBLISH_NO_PARENT_ADDRESS = 'A page above this one is not published, so this page has no address yet. Publish that page first.';
|
|
22
|
+
/** Kinds of screen whose Publish is the Besigner's alone, each with why. */ const PUBLISHED_IN_THE_BESIGNER = {
|
|
23
|
+
[SCREEN_KIND_TEMPLATE]: 'A collection template is published from the Besigner.',
|
|
24
|
+
[SCREEN_KIND_ERROR]: 'An error page is published from the Besigner.',
|
|
25
|
+
[SCREEN_KIND_GROUP]: 'A page group has no version to publish.',
|
|
26
|
+
email: 'An email is sent by campaigns, not published as a page.'
|
|
27
|
+
};
|
|
28
|
+
export const screenVersionPublishTakenCopy = (path)=>`Another page is already published at ${screenRoutePathToUrl(path)}. Give this one another address in the Besigner.`;
|
|
29
|
+
export function screenVersionPublish(input) {
|
|
30
|
+
const { screenId, versionId, screen, routingMap } = input;
|
|
31
|
+
if (!screen || screen.deletedAt != null) return {
|
|
32
|
+
kind: 'blocked',
|
|
33
|
+
reason: SCREEN_VERSION_PUBLISH_DELETED
|
|
34
|
+
};
|
|
35
|
+
const elsewhere = typeof screen.kind === 'string' ? PUBLISHED_IN_THE_BESIGNER[screen.kind] : undefined;
|
|
36
|
+
if (elsewhere) return {
|
|
37
|
+
kind: 'blocked',
|
|
38
|
+
reason: elsewhere
|
|
39
|
+
};
|
|
40
|
+
const routed = routingMap == null ? void 0 : routingMap[screenId];
|
|
41
|
+
if (typeof routed === 'string') {
|
|
42
|
+
return screen.versionId === versionId ? {
|
|
43
|
+
kind: 'live',
|
|
44
|
+
path: routed
|
|
45
|
+
} : {
|
|
46
|
+
kind: 'replace',
|
|
47
|
+
path: routed,
|
|
48
|
+
placeholder: screenId === input.defaultHomeScreenId
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const slug = normalizeScreenSlug(typeof screen.slug === 'string' ? screen.slug : '');
|
|
52
|
+
if (!slug) return {
|
|
53
|
+
kind: 'blocked',
|
|
54
|
+
reason: SCREEN_VERSION_PUBLISH_NO_ADDRESS
|
|
55
|
+
};
|
|
56
|
+
const path = composeScreenRoutePath(screenId, _extends({}, input.screensById, {
|
|
57
|
+
[screenId]: _extends({}, toScreenRouteNode(screen), {
|
|
58
|
+
slug
|
|
59
|
+
})
|
|
60
|
+
}));
|
|
61
|
+
if (!path) return {
|
|
62
|
+
kind: 'blocked',
|
|
63
|
+
reason: SCREEN_VERSION_PUBLISH_NO_PARENT_ADDRESS
|
|
64
|
+
};
|
|
65
|
+
const reserved = reservedScreenRouteSegment(path);
|
|
66
|
+
if (reserved) return {
|
|
67
|
+
kind: 'blocked',
|
|
68
|
+
reason: reservedScreenRouteMessage(reserved)
|
|
69
|
+
};
|
|
70
|
+
const owner = blockingRouteOwner(routingMap, path, input.defaultHomeScreenId);
|
|
71
|
+
if (owner && owner !== screenId) return {
|
|
72
|
+
kind: 'blocked',
|
|
73
|
+
reason: screenVersionPublishTakenCopy(path)
|
|
74
|
+
};
|
|
75
|
+
return {
|
|
76
|
+
kind: 'first',
|
|
77
|
+
slug,
|
|
78
|
+
path
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
//# sourceMappingURL=screen-version-publish.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/screen-version-publish.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n blockingRouteOwner,\n composeScreenRoutePath,\n SCREEN_KIND_ERROR,\n SCREEN_KIND_GROUP,\n SCREEN_KIND_TEMPLATE,\n screenRoutePathToUrl,\n toScreenRouteNode,\n type ScreenRouteNode,\n} from './screen-route'\nimport { normalizeScreenSlug, reservedScreenRouteMessage, reservedScreenRouteSegment } from './screen-slug'\n\n/**\n * What Publish does to one version of a page, from outside the Besigner\n * (AGL-3616): the preview of a version an AI job wrote leads with it.\n *\n * - `live` — the site already serves this version at `path`; nothing to do.\n * - `replace` — the page is live with another version: Publish makes this\n * one the version the site serves at `path`. `placeholder` where the page\n * is the site's placeholder home (AGL-3478), which publishes through the\n * route seam so it becomes the owner's.\n * - `first` — the page is not on the site: Publish puts it at `path`, its\n * own `slug` under its parents, with this version.\n * - `blocked` — Publish cannot be done from here, and `reason` says why in\n * the words the person reads. The Besigner is where each is fixed.\n *\n * The same refusals, in the same order, as the page's own Publish: no\n * address, a parent with none, a reserved address, an address another page\n * holds.\n */\nexport type ScreenVersionPublish =\n | { kind: 'live'; path: string }\n | { kind: 'replace'; path: string; placeholder: boolean }\n | { kind: 'first'; slug: string; path: string }\n | { kind: 'blocked'; reason: string }\n\nexport const SCREEN_VERSION_PUBLISH_DELETED = 'This page was deleted.'\nexport const SCREEN_VERSION_PUBLISH_NO_ADDRESS =\n 'This page has no address yet. Give it one in the Besigner, then publish it.'\nexport const SCREEN_VERSION_PUBLISH_NO_PARENT_ADDRESS =\n 'A page above this one is not published, so this page has no address yet. Publish that page first.'\n/** Kinds of screen whose Publish is the Besigner's alone, each with why. */\nconst PUBLISHED_IN_THE_BESIGNER: Readonly<Record<string, string>> = {\n [SCREEN_KIND_TEMPLATE]: 'A collection template is published from the Besigner.',\n [SCREEN_KIND_ERROR]: 'An error page is published from the Besigner.',\n [SCREEN_KIND_GROUP]: 'A page group has no version to publish.',\n email: 'An email is sent by campaigns, not published as a page.',\n}\n\nexport const screenVersionPublishTakenCopy = (path: string): string =>\n `Another page is already published at ${screenRoutePathToUrl(path)}. Give this one another address in the Besigner.`\n\nexport function screenVersionPublish(input: {\n screenId: string\n versionId: string\n /** The page's document, `null` where it is gone. */\n screen: { versionId?: unknown; slug?: unknown; kind?: unknown; parentId?: unknown; deletedAt?: unknown } | null\n /** The site's routing map, `host.screens`. */\n routingMap: Readonly<Record<string, string>> | null | undefined\n /** Every page of the site as path composition reads it (`toScreenRouteNode`). */\n screensById: Readonly<Record<string, ScreenRouteNode | undefined>>\n defaultHomeScreenId?: string | null\n}): ScreenVersionPublish {\n const { screenId, versionId, screen, routingMap } = input\n if (!screen || screen.deletedAt != null) return { kind: 'blocked', reason: SCREEN_VERSION_PUBLISH_DELETED }\n const elsewhere = typeof screen.kind === 'string' ? PUBLISHED_IN_THE_BESIGNER[screen.kind] : undefined\n if (elsewhere) return { kind: 'blocked', reason: elsewhere }\n const routed = routingMap?.[screenId]\n if (typeof routed === 'string') {\n return screen.versionId === versionId\n ? { kind: 'live', path: routed }\n : { kind: 'replace', path: routed, placeholder: screenId === input.defaultHomeScreenId }\n }\n const slug = normalizeScreenSlug(typeof screen.slug === 'string' ? screen.slug : '')\n if (!slug) return { kind: 'blocked', reason: SCREEN_VERSION_PUBLISH_NO_ADDRESS }\n const path = composeScreenRoutePath(screenId, {\n ...input.screensById,\n [screenId]: { ...toScreenRouteNode(screen), slug },\n })\n if (!path) return { kind: 'blocked', reason: SCREEN_VERSION_PUBLISH_NO_PARENT_ADDRESS }\n const reserved = reservedScreenRouteSegment(path)\n if (reserved) return { kind: 'blocked', reason: reservedScreenRouteMessage(reserved) }\n const owner = blockingRouteOwner(routingMap, path, input.defaultHomeScreenId)\n if (owner && owner !== screenId) return { kind: 'blocked', reason: screenVersionPublishTakenCopy(path) }\n return { kind: 'first', slug, path }\n}\n"],"names":["blockingRouteOwner","composeScreenRoutePath","SCREEN_KIND_ERROR","SCREEN_KIND_GROUP","SCREEN_KIND_TEMPLATE","screenRoutePathToUrl","toScreenRouteNode","normalizeScreenSlug","reservedScreenRouteMessage","reservedScreenRouteSegment","SCREEN_VERSION_PUBLISH_DELETED","SCREEN_VERSION_PUBLISH_NO_ADDRESS","SCREEN_VERSION_PUBLISH_NO_PARENT_ADDRESS","PUBLISHED_IN_THE_BESIGNER","email","screenVersionPublishTakenCopy","path","screenVersionPublish","input","screenId","versionId","screen","routingMap","deletedAt","kind","reason","elsewhere","undefined","routed","placeholder","defaultHomeScreenId","slug","screensById","reserved","owner"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,kBAAkB,EAClBC,sBAAsB,EACtBC,iBAAiB,EACjBC,iBAAiB,EACjBC,oBAAoB,EACpBC,oBAAoB,EACpBC,iBAAiB,QAEZ,oBAAgB;AACvB,SAASC,mBAAmB,EAAEC,0BAA0B,EAAEC,0BAA0B,QAAQ,mBAAe;AA0B3G,OAAO,MAAMC,iCAAiC,yBAAwB;AACtE,OAAO,MAAMC,oCACX,8EAA6E;AAC/E,OAAO,MAAMC,2CACX,oGAAmG;AACrG,0EAA0E,GAC1E,MAAMC,4BAA8D;IAClE,CAACT,qBAAqB,EAAE;IACxB,CAACF,kBAAkB,EAAE;IACrB,CAACC,kBAAkB,EAAE;IACrBW,OAAO;AACT;AAEA,OAAO,MAAMC,gCAAgC,CAACC,OAC5C,CAAC,qCAAqC,EAAEX,qBAAqBW,MAAM,gDAAgD,CAAC,CAAA;AAEtH,OAAO,SAASC,qBAAqBC,KAUpC;IACC,MAAM,EAAEC,QAAQ,EAAEC,SAAS,EAAEC,MAAM,EAAEC,UAAU,EAAE,GAAGJ;IACpD,IAAI,CAACG,UAAUA,OAAOE,SAAS,IAAI,MAAM,OAAO;QAAEC,MAAM;QAAWC,QAAQf;IAA+B;IAC1G,MAAMgB,YAAY,OAAOL,OAAOG,IAAI,KAAK,WAAWX,yBAAyB,CAACQ,OAAOG,IAAI,CAAC,GAAGG;IAC7F,IAAID,WAAW,OAAO;QAAEF,MAAM;QAAWC,QAAQC;IAAU;IAC3D,MAAME,SAASN,8BAAAA,UAAY,CAACH,SAAS;IACrC,IAAI,OAAOS,WAAW,UAAU;QAC9B,OAAOP,OAAOD,SAAS,KAAKA,YACxB;YAAEI,MAAM;YAAQR,MAAMY;QAAO,IAC7B;YAAEJ,MAAM;YAAWR,MAAMY;YAAQC,aAAaV,aAAaD,MAAMY,mBAAmB;QAAC;IAC3F;IACA,MAAMC,OAAOxB,oBAAoB,OAAOc,OAAOU,IAAI,KAAK,WAAWV,OAAOU,IAAI,GAAG;IACjF,IAAI,CAACA,MAAM,OAAO;QAAEP,MAAM;QAAWC,QAAQd;IAAkC;IAC/E,MAAMK,OAAOf,uBAAuBkB,UAAU,aACzCD,MAAMc,WAAW;QACpB,CAACb,SAAS,EAAE,aAAKb,kBAAkBe;YAASU;;;IAE9C,IAAI,CAACf,MAAM,OAAO;QAAEQ,MAAM;QAAWC,QAAQb;IAAyC;IACtF,MAAMqB,WAAWxB,2BAA2BO;IAC5C,IAAIiB,UAAU,OAAO;QAAET,MAAM;QAAWC,QAAQjB,2BAA2ByB;IAAU;IACrF,MAAMC,QAAQlC,mBAAmBsB,YAAYN,MAAME,MAAMY,mBAAmB;IAC5E,IAAII,SAASA,UAAUf,UAAU,OAAO;QAAEK,MAAM;QAAWC,QAAQV,8BAA8BC;IAAM;IACvG,OAAO;QAAEQ,MAAM;QAASO;QAAMf;IAAK;AACrC"}
|
|
@@ -128,6 +128,7 @@ export * from './ensure-canvas-root';
|
|
|
128
128
|
export * from './repair-canvas-nodes';
|
|
129
129
|
export * from './detect-template-placeholders';
|
|
130
130
|
export * from './guarded-version-save';
|
|
131
|
+
export * from './version-draft';
|
|
131
132
|
export * from './local-storage-budget';
|
|
132
133
|
export * from './measure-node-map';
|
|
133
134
|
export * from './resolve-named-tokens';
|
|
@@ -263,6 +263,7 @@ export * from "./ensure-canvas-root.js";
|
|
|
263
263
|
export * from "./repair-canvas-nodes.js";
|
|
264
264
|
export * from "./detect-template-placeholders.js";
|
|
265
265
|
export * from "./guarded-version-save.js";
|
|
266
|
+
export * from "./version-draft.js";
|
|
266
267
|
export * from "./local-storage-budget.js";
|
|
267
268
|
export * from "./measure-node-map.js";
|
|
268
269
|
export * from "./resolve-named-tokens.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/server.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2022 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Server-safe app-utils: every app-util EXCEPT the client-only React\n * contexts (see ./contexts). Nothing here calls `createContext` at module\n * scope, so this barrel — re-exported by `@aglyn/aglyn/server` — is safe to\n * import from tenant Server Components (AGL-405). The full `./index` barrel\n * re-adds the contexts for client consumers.\n */\nexport * from './binding-token-catalog'\nexport * from './binding-tokens'\nexport * from './breadcrumb-json-ld'\n// What a search listing holds and how long each field may be (AGL-2910):\n// the SEO editors and anything that proposes a value read one catalog.\nexport * from './seo-listing-fields'\n// `VideoObject` for the Video elements a page actually ships (AGL-2747).\n// Beside the other structured-data builders, and server-only like them: only\n// the route emits JSON-LD, and a crawler-facing block has no client caller.\nexport * from './video-object'\n// `FAQPage` for the FAQ section a page draws (AGL-3574): the same input, the\n// same reason, and server-only for the same one — only the route emits it.\nexport * from './faq-page'\nexport * from './collection-delete'\nexport * from './collection-entries'\nexport * from './collection-kind'\nexport * from './collection-slug'\nexport * from './content-authors'\n// NOT re-exported: `./local-business` and `./site-entity-json-ld` (AGL-3383).\n// This barrel is the whole namespace a realm bundle is handed and part of the\n// runtime a published page loads (`check:tenant-wire-weight`); the site\n// entity's JSON-LD and the LocalBusiness allow-list are read only by the\n// tenant page's server render and the console's Setup → SEO card, which import\n// them by path (`@aglyn/aglyn/app-utils/...`).\nexport * from './content-query-fields'\nexport * from './content-schema-type'\nexport * from './content-author-profile'\nexport * from './url-slug'\nexport * from './child-contract'\nexport * from './child-contract-compose'\n// The one reading of `restrictChildren`/`restrictParent`, shared by the\n// besigner's drop check and the AI plugin's node-tree validator (AGL-2905).\nexport * from './lineal-order'\n// What a portable node tree must satisfy before it lands on a canvas that did\n// not author it (AGL-2939): a tree installed from another workspace and a tree\n// a model generated both pass through it.\nexport * from './node-definition-sanitizer'\nexport * from './console-routes'\n// What a stored screen-link value means, with no React attached — the\n// where-used scan reads these on the server (AGL-703).\nexport * from './screen-link-value'\n// Which entries a page's links name (AGL-3118). The tenant render reads\n// exactly those entry documents, so the walk runs on the server.\nexport * from './link-references'\nexport * from './analytics-path-key'\nexport * from './contacts'\n// A lead's or a contact's email state (AGL-3245): the last verdict on its\n// address, in words a person can act on, mirrored from the senders' lists.\nexport * from './email-state'\n// A field whose choices an admin keeps (AGL-3510): standard values built in,\n// the organization's own beside them, and the judge every write asks. Pure,\n// and named for no plugin — a plugin's registry of picklists sits on top.\nexport * from './picklists'\n// The records that sit beside a contact — companies, pipelines, deals, tasks,\n// activities, field definitions (AGL-2595). Pure like `contacts`, and beside\n// it because `ContactFacet` names its lifecycle stage and custom-value types;\n// the scope stamp every CRM creator writes lives here too.\nexport * from './crm'\n// Email templates, snippets and the merge-field resolver (AGL-2658): the\n// letter a rep keeps under a name, and the one grammar the send route and\n// the dialog fill it in by. Pure like `crm`, and beside it because a\n// template is scoped the way a task is.\nexport * from './crm-email-templates'\n// A person's privacy erasure from one workspace (AGL-2623): the request's\n// shape and id, the marker a record carries while it waits, and the lists\n// the dialog and the docs share. Pure like `crm`; the sweep is server-side.\nexport * from './person-erasure'\n// A task's reminder at its own time (AGL-2659): which reminder a save\n// leaves, whether one is owed, and the words the notification and the mail\n// say. Pure like `crm`, read by the hourly route and by every writer of the\n// field.\n// Reading a received email (AGL-2657): the address in a header, the thread\n// a subject belongs to, the reply above the quoted history — and the domain\n// the platform receives mail on. Pure; every surface that reads mail it was\n// sent reads it this way.\nexport * from './email-text'\nexport * from './inbound-mail-domain'\n// A member's own addresses in a workspace (AGL-2975): the ones they have\n// confirmed count as theirs wherever the roster is asked who wrote a\n// message. Pure; the store and the confirmation link are server-side.\nexport * from './member-email-aliases'\n// The CSV cell grammar the exports write with (AGL-2621), and the target a\n// mapped column names for a custom field — named rather than starred, because\n// an importer built on this file exports the shared ceilings under its own\n// names.\nexport {\n csvCell,\n csvDocument,\n customImportTarget,\n customImportTargetKey,\n importNoMailServerSentence,\n} from './csv-import'\nexport type {\n ImportChunkResult,\n ImportDroppedValue,\n ImportSkippedRow,\n} from './csv-import'\n// What a VALUE under a custom contact field may be, and the coercion every\n// door applies to one (AGL-2601): a mapped form field, an API `custom` body,\n// and the carry-over that keeps a mapping across a publish. Pure like `crm`.\nexport * from './contact-custom-fields'\n// The consent JOIN (`docs/specs/email-overhaul.md` §3f). Pure, and it composes\n// `contacts` for the normalizer, so it sits directly beside it. No Node\n// builtin, which is what keeps it out of the `/server`-only group `person-key`\n// belongs to.\nexport * from './consent-groups'\nexport * from './marketing-consent'\n// A refusal kept after the record it was written on is deleted (AGL-3338),\n// read back through the same parser as the record's own.\nexport * from './retained-refusals'\n// The ENROLLMENT-time half of the same question, beside its reader for the\n// same reason: `marketing-consent` decides whether a recorded basis lets us\n// mail somebody, and this decides what basis putting them on a list may\n// record. Two enrollment surfaces import it — the Inbox assignment route and\n// the Emails console's audience card — and neither could import the other.\nexport * from './enrollment-basis'\n// Reading the FILE a merchant arrives with (`docs/specs/email-competitive-gaps.md`\n// G5/P4), directly after the policy it hands its addresses to: parsing and\n// mechanical screening only, so that an import asks the enrollment question\n// through the same module the one-address add path asks it through rather\n// than answering it a second way.\n// The dynamic-list rule (§3b/§3c), beside it for the same reasons: pure, and\n// it composes `contacts` for the segment vocabulary rather than restating it.\nexport * from './dynamic-list-rule'\n// The subscribable streams a recipient can leave one at a time\n// (`docs/specs/email-competitive-gaps.md` §1f). Pure, and read from all three\n// sides of the feature: the composer picks one, the send path signs it into\n// the unsubscribe link, and the unauthenticated preference page renders the\n// catalog. No Node builtin, so it stays out of the `/server`-only group.\nexport * from './subscription-topics'\nexport * from './compose-layout-nodes'\n// Layout properties applied through the chain (AGL-2893).\nexport * from './compose-layout-props'\n// A screen's per-page restyling of its layout's elements (AGL-3286).\nexport * from './layout-style-overrides'\nexport * from './document-landmark'\nexport * from './functions'\nexport * from './compose-reusable-components'\n// Where a reusable component is placed — pages or emails (AGL-3287).\nexport * from './reusable-component-kind'\nexport * from './compress'\nexport * from './merge-node-sx'\nexport * from './palette-sx'\nexport * from './scheme-sx'\n// An element pinned to one color scheme (AGL-3284).\nexport * from './element-color-scheme'\nexport * from './create-resource-uid'\nexport * from './decompress'\nexport * from './stored-nodes'\nexport * from './strip-undefined'\nexport * from './organizations'\nexport * from './org-permissions'\n// The AI verdict on either membership axis (AGL-2927). After both modules it\n// composes: the org catalog for org-wide members, the host role for site\n// collaborators.\nexport * from './host-permissions'\nexport * from './password-policy'\nexport * from './idp-profile'\nexport * from './onboarding-deep-link'\n// The platform team's ask that a workspace buy a plan (AGL-3466), beside the\n// plan intent its button carries into Billing.\nexport * from './upgrade-proposal'\n// Where an account came from (AGL-1731). Beside the plan intent because\n// they are the same hop — the marketing CTA's query string — and both are\n// remembered on `users/{uid}` across the verification wall.\nexport * from './utm-attribution'\n// What a record is filed under: the edge between a container a plugin keeps\n// (a campaign) and the forms, screens and contacts it gathers. Beside the\n// attribution above because the two answer opposite questions: attribution\n// records where somebody CAME from, this records what a merchant DECLARED\n// belongs together.\nexport * from './container-membership'\nexport * from './deployment-shape'\n// Which browser origins may complete a signed direct-to-GCS upload (AGL-1452).\n// GCS matches the origin list EXACTLY, so every serving console name needs its\n// own entry — the rule nobody could be expected to remember at attach time.\nexport * from './upload-cors'\n// The docs-help subset the first-party plugin consoles read (AGL-2213) — the\n// console's own registry lives in apps/console/constants and a lib cannot\n// import an app.\nexport * from './docs-help'\nexport * from './platform-brand'\nexport * from './plan-entitlements'\n// The free plan's bandwidth hard cap (AGL-1967/2070/2155). After\n// `plan-entitlements`, which owns the predicate it keys off.\nexport * from './bandwidth-cap'\n// Video and file delivery as bandwidth (AGL-3474), in the band's own units.\nexport * from './media-bandwidth'\n// The month every monthly counter is keyed by, and the boundary it lifts on.\nexport * from './utc-month'\nexport * from './forms'\n// What a form's DESIGN must still satisfy for its submissions to arrive.\n// After `forms`, whose field walk it reads the drawn fields with.\nexport * from './form-contract'\nexport * from './visitor-record-ceiling'\nexport * from './health-report'\n// Did a webhook delivery actually DO anything (AGL-1954)? After\n// `health-report`, whose billing verdict consumes the counts this produces.\nexport * from './webhook-delivery'\nexport * from './support-tiers'\n// Site Care requests (AGL-3959): the arithmetic, and the two modules any\n// staff-recorded, approval-gated charge reuses (AGL-3970).\nexport * from './site-care'\nexport * from './owner-approval-record'\nexport * from './append-only-history'\n// Site Care projects (AGL-3970): the flat quote, its split and its record.\nexport * from './site-care-project'\nexport * from './release-flags'\n// Who runs THIS deployment, and where its notices go (AGL-2016). Ordered\n// before the four surfaces that print it, all of which used to declare their\n// own `support@aglyn.com` literal.\nexport * from './operator-identity'\nexport * from './lockdown'\nexport * from './media-quarantine'\nexport * from './media-takedown-reach'\nexport * from './upload-inspection'\nexport * from './abuse-report'\n// The other three quarters of §512 (AGL-1983): the put-back procedure that\n// answers a notice, and the strike ledger that conditions the safe harbour.\nexport * from './dmca-counter-notice'\nexport * from './repeat-infringer'\n// `addBusinessDays` briefly existed TWICE — once here in './support-tiers'\n// and once in './dmca-counter-notice' — and two `export *` made the name\n// ambiguous, TS2308'ing this barrel and every tsconfig that reads it. The\n// interleave was fixed twice in parallel: once by naming a winner here, and\n// once by deleting the second definition so './dmca-counter-notice' imports\n// this one. Only the deletion survives, so the disambiguating re-export that\n// stood on this line is gone with it — `export *` is unambiguous again.\n//\n// The concern that argued for keeping two copies is worth answering rather\n// than dropping: a later tweak to the SUPPORT SLA window must never silently\n// move the §512(g)(2)(C) put-back deadline. It cannot. This function embeds\n// no window — it is \"add N business days\", and N is the argument. The two\n// windows live in their own constants (`SUPPORT_*` there,\n// `COUNTER_NOTICE_*_BUSINESS_DAYS` in the DMCA module) and never meet. What\n// SHOULD stay shared is the day arithmetic itself: two copies of a weekend\n// rule are two chances to disagree about which day a deadline falls on.\nexport * from './org-override-reason'\nexport * from './host-tokens'\nexport * from './variables'\n// What happened on a site that an automation can start on: the platform's\n// own event and the ones plugins declare (AGL-3080).\nexport * from './host-events'\nexport * from './expand-repeatables'\nexport * from './org-roles'\nexport * from './markdown-lite'\nexport * from './notifications'\n// The operator alert registry (AGL-3377); OPERATOR_ALERT_NOTIFICATION_TYPES\n// lives here now, derived from it.\nexport * from './operator-alerts'\nexport * from './definition-canvas-tree'\nexport * from './ensure-canvas-root'\nexport * from './repair-canvas-nodes'\nexport * from './detect-template-placeholders'\nexport * from './guarded-version-save'\nexport * from './local-storage-budget'\nexport * from './measure-node-map'\nexport * from './resolve-named-tokens'\nexport * from './screen-route'\nexport * from './host-naming'\nexport * from './name-search'\nexport * from './artifact-list-keys'\nexport * from './name-match'\nexport * from './api-plugins'\nexport * from './plugin-api-rate-limit'\n// The same-origin gate on visitor-facing plugin writes (AGL-1880). After\n// `plugin-api-rate-limit`, whose `isMachinePluginApiPath` it reuses so the\n// two gates exempt exactly the same machine surfaces.\nexport * from './plugin-api-cross-origin'\n// What a published page does when a visitor does something (AGL-3080): the\n// platform's interaction vocabulary, which the workflows plugin builds its\n// automations on.\nexport * from './site-interactions'\nexport * from './element-animation'\nexport * from './attribution-guard'\nexport * from './node-interactions'\nexport * from './element-ui'\nexport * from './image-upload-types'\nexport * from './sanitize-svg'\nexport * from './media-folders'\nexport * from './media-metadata'\nexport * from './media-ref'\nexport * from './rendered-widths'\n// After `media-ref`, whose reference grammar it reads stored URLs with.\nexport * from './paid-media-source'\nexport * from './author-css'\n// The isomorphic HTML rule (AGL-1901), after the CSS one it depends on.\nexport * from './author-html'\nexport * from './csv'\nexport * from './csv-upload'\nexport * from './artifact-provenance'\nexport * from './repeat-query'\nexport * from './plugin-manifest'\n// After `plugin-manifest`, whose revocation predicates it asks the kill\n// question with, and after `media-ref`, whose grammar decides what counts as a\n// first-party image.\nexport * from './email-starter-policy'\nexport * from './safe-json-ld'\n// Agent readiness (AGL-2716): content negotiation, the Markdown rendering of a\n// page, `/llms.txt` and the per-site OpenAPI document. All pure, all server-safe.\nexport * from './accept-negotiation'\nexport * from './agent-api-catalog'\nexport * from './agent-openapi'\nexport * from './author-html-markdown'\nexport * from './llms-txt'\nexport * from './page-markdown'\nexport * from './request-geo'\nexport * from './request-ip'\nexport * from './search-indexing'\n// Preview: a site or page only its team can see (AGL-4086).\nexport * from './site-preview'\nexport * from './seo-title'\nexport * from './sitemap'\nexport * from './visitor-consent'\nexport * from './social-image'\nexport * from './scope-tokens'\nexport * from './org-billing-doc'\nexport * from './stripe-deployment-mode'\nexport * from './deferred-image'\n"],"names":["csvCell","csvDocument","customImportTarget","customImportTargetKey","importNoMailServerSentence"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/server.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2022 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Server-safe app-utils: every app-util EXCEPT the client-only React\n * contexts (see ./contexts). Nothing here calls `createContext` at module\n * scope, so this barrel — re-exported by `@aglyn/aglyn/server` — is safe to\n * import from tenant Server Components (AGL-405). The full `./index` barrel\n * re-adds the contexts for client consumers.\n */\nexport * from './binding-token-catalog'\nexport * from './binding-tokens'\nexport * from './breadcrumb-json-ld'\n// What a search listing holds and how long each field may be (AGL-2910):\n// the SEO editors and anything that proposes a value read one catalog.\nexport * from './seo-listing-fields'\n// `VideoObject` for the Video elements a page actually ships (AGL-2747).\n// Beside the other structured-data builders, and server-only like them: only\n// the route emits JSON-LD, and a crawler-facing block has no client caller.\nexport * from './video-object'\n// `FAQPage` for the FAQ section a page draws (AGL-3574): the same input, the\n// same reason, and server-only for the same one — only the route emits it.\nexport * from './faq-page'\nexport * from './collection-delete'\nexport * from './collection-entries'\nexport * from './collection-kind'\nexport * from './collection-slug'\nexport * from './content-authors'\n// NOT re-exported: `./local-business` and `./site-entity-json-ld` (AGL-3383).\n// This barrel is the whole namespace a realm bundle is handed and part of the\n// runtime a published page loads (`check:tenant-wire-weight`); the site\n// entity's JSON-LD and the LocalBusiness allow-list are read only by the\n// tenant page's server render and the console's Setup → SEO card, which import\n// them by path (`@aglyn/aglyn/app-utils/...`).\nexport * from './content-query-fields'\nexport * from './content-schema-type'\nexport * from './content-author-profile'\nexport * from './url-slug'\nexport * from './child-contract'\nexport * from './child-contract-compose'\n// The one reading of `restrictChildren`/`restrictParent`, shared by the\n// besigner's drop check and the AI plugin's node-tree validator (AGL-2905).\nexport * from './lineal-order'\n// What a portable node tree must satisfy before it lands on a canvas that did\n// not author it (AGL-2939): a tree installed from another workspace and a tree\n// a model generated both pass through it.\nexport * from './node-definition-sanitizer'\nexport * from './console-routes'\n// What a stored screen-link value means, with no React attached — the\n// where-used scan reads these on the server (AGL-703).\nexport * from './screen-link-value'\n// Which entries a page's links name (AGL-3118). The tenant render reads\n// exactly those entry documents, so the walk runs on the server.\nexport * from './link-references'\nexport * from './analytics-path-key'\nexport * from './contacts'\n// A lead's or a contact's email state (AGL-3245): the last verdict on its\n// address, in words a person can act on, mirrored from the senders' lists.\nexport * from './email-state'\n// A field whose choices an admin keeps (AGL-3510): standard values built in,\n// the organization's own beside them, and the judge every write asks. Pure,\n// and named for no plugin — a plugin's registry of picklists sits on top.\nexport * from './picklists'\n// The records that sit beside a contact — companies, pipelines, deals, tasks,\n// activities, field definitions (AGL-2595). Pure like `contacts`, and beside\n// it because `ContactFacet` names its lifecycle stage and custom-value types;\n// the scope stamp every CRM creator writes lives here too.\nexport * from './crm'\n// Email templates, snippets and the merge-field resolver (AGL-2658): the\n// letter a rep keeps under a name, and the one grammar the send route and\n// the dialog fill it in by. Pure like `crm`, and beside it because a\n// template is scoped the way a task is.\nexport * from './crm-email-templates'\n// A person's privacy erasure from one workspace (AGL-2623): the request's\n// shape and id, the marker a record carries while it waits, and the lists\n// the dialog and the docs share. Pure like `crm`; the sweep is server-side.\nexport * from './person-erasure'\n// A task's reminder at its own time (AGL-2659): which reminder a save\n// leaves, whether one is owed, and the words the notification and the mail\n// say. Pure like `crm`, read by the hourly route and by every writer of the\n// field.\n// Reading a received email (AGL-2657): the address in a header, the thread\n// a subject belongs to, the reply above the quoted history — and the domain\n// the platform receives mail on. Pure; every surface that reads mail it was\n// sent reads it this way.\nexport * from './email-text'\nexport * from './inbound-mail-domain'\n// A member's own addresses in a workspace (AGL-2975): the ones they have\n// confirmed count as theirs wherever the roster is asked who wrote a\n// message. Pure; the store and the confirmation link are server-side.\nexport * from './member-email-aliases'\n// The CSV cell grammar the exports write with (AGL-2621), and the target a\n// mapped column names for a custom field — named rather than starred, because\n// an importer built on this file exports the shared ceilings under its own\n// names.\nexport {\n csvCell,\n csvDocument,\n customImportTarget,\n customImportTargetKey,\n importNoMailServerSentence,\n} from './csv-import'\nexport type {\n ImportChunkResult,\n ImportDroppedValue,\n ImportSkippedRow,\n} from './csv-import'\n// What a VALUE under a custom contact field may be, and the coercion every\n// door applies to one (AGL-2601): a mapped form field, an API `custom` body,\n// and the carry-over that keeps a mapping across a publish. Pure like `crm`.\nexport * from './contact-custom-fields'\n// The consent JOIN (`docs/specs/email-overhaul.md` §3f). Pure, and it composes\n// `contacts` for the normalizer, so it sits directly beside it. No Node\n// builtin, which is what keeps it out of the `/server`-only group `person-key`\n// belongs to.\nexport * from './consent-groups'\nexport * from './marketing-consent'\n// A refusal kept after the record it was written on is deleted (AGL-3338),\n// read back through the same parser as the record's own.\nexport * from './retained-refusals'\n// The ENROLLMENT-time half of the same question, beside its reader for the\n// same reason: `marketing-consent` decides whether a recorded basis lets us\n// mail somebody, and this decides what basis putting them on a list may\n// record. Two enrollment surfaces import it — the Inbox assignment route and\n// the Emails console's audience card — and neither could import the other.\nexport * from './enrollment-basis'\n// Reading the FILE a merchant arrives with (`docs/specs/email-competitive-gaps.md`\n// G5/P4), directly after the policy it hands its addresses to: parsing and\n// mechanical screening only, so that an import asks the enrollment question\n// through the same module the one-address add path asks it through rather\n// than answering it a second way.\n// The dynamic-list rule (§3b/§3c), beside it for the same reasons: pure, and\n// it composes `contacts` for the segment vocabulary rather than restating it.\nexport * from './dynamic-list-rule'\n// The subscribable streams a recipient can leave one at a time\n// (`docs/specs/email-competitive-gaps.md` §1f). Pure, and read from all three\n// sides of the feature: the composer picks one, the send path signs it into\n// the unsubscribe link, and the unauthenticated preference page renders the\n// catalog. No Node builtin, so it stays out of the `/server`-only group.\nexport * from './subscription-topics'\nexport * from './compose-layout-nodes'\n// Layout properties applied through the chain (AGL-2893).\nexport * from './compose-layout-props'\n// A screen's per-page restyling of its layout's elements (AGL-3286).\nexport * from './layout-style-overrides'\nexport * from './document-landmark'\nexport * from './functions'\nexport * from './compose-reusable-components'\n// Where a reusable component is placed — pages or emails (AGL-3287).\nexport * from './reusable-component-kind'\nexport * from './compress'\nexport * from './merge-node-sx'\nexport * from './palette-sx'\nexport * from './scheme-sx'\n// An element pinned to one color scheme (AGL-3284).\nexport * from './element-color-scheme'\nexport * from './create-resource-uid'\nexport * from './decompress'\nexport * from './stored-nodes'\nexport * from './strip-undefined'\nexport * from './organizations'\nexport * from './org-permissions'\n// The AI verdict on either membership axis (AGL-2927). After both modules it\n// composes: the org catalog for org-wide members, the host role for site\n// collaborators.\nexport * from './host-permissions'\nexport * from './password-policy'\nexport * from './idp-profile'\nexport * from './onboarding-deep-link'\n// The platform team's ask that a workspace buy a plan (AGL-3466), beside the\n// plan intent its button carries into Billing.\nexport * from './upgrade-proposal'\n// Where an account came from (AGL-1731). Beside the plan intent because\n// they are the same hop — the marketing CTA's query string — and both are\n// remembered on `users/{uid}` across the verification wall.\nexport * from './utm-attribution'\n// What a record is filed under: the edge between a container a plugin keeps\n// (a campaign) and the forms, screens and contacts it gathers. Beside the\n// attribution above because the two answer opposite questions: attribution\n// records where somebody CAME from, this records what a merchant DECLARED\n// belongs together.\nexport * from './container-membership'\nexport * from './deployment-shape'\n// Which browser origins may complete a signed direct-to-GCS upload (AGL-1452).\n// GCS matches the origin list EXACTLY, so every serving console name needs its\n// own entry — the rule nobody could be expected to remember at attach time.\nexport * from './upload-cors'\n// The docs-help subset the first-party plugin consoles read (AGL-2213) — the\n// console's own registry lives in apps/console/constants and a lib cannot\n// import an app.\nexport * from './docs-help'\nexport * from './platform-brand'\nexport * from './plan-entitlements'\n// The free plan's bandwidth hard cap (AGL-1967/2070/2155). After\n// `plan-entitlements`, which owns the predicate it keys off.\nexport * from './bandwidth-cap'\n// Video and file delivery as bandwidth (AGL-3474), in the band's own units.\nexport * from './media-bandwidth'\n// The month every monthly counter is keyed by, and the boundary it lifts on.\nexport * from './utc-month'\nexport * from './forms'\n// What a form's DESIGN must still satisfy for its submissions to arrive.\n// After `forms`, whose field walk it reads the drawn fields with.\nexport * from './form-contract'\nexport * from './visitor-record-ceiling'\nexport * from './health-report'\n// Did a webhook delivery actually DO anything (AGL-1954)? After\n// `health-report`, whose billing verdict consumes the counts this produces.\nexport * from './webhook-delivery'\nexport * from './support-tiers'\n// Site Care requests (AGL-3959): the arithmetic, and the two modules any\n// staff-recorded, approval-gated charge reuses (AGL-3970).\nexport * from './site-care'\nexport * from './owner-approval-record'\nexport * from './append-only-history'\n// Site Care projects (AGL-3970): the flat quote, its split and its record.\nexport * from './site-care-project'\nexport * from './release-flags'\n// Who runs THIS deployment, and where its notices go (AGL-2016). Ordered\n// before the four surfaces that print it, all of which used to declare their\n// own `support@aglyn.com` literal.\nexport * from './operator-identity'\nexport * from './lockdown'\nexport * from './media-quarantine'\nexport * from './media-takedown-reach'\nexport * from './upload-inspection'\nexport * from './abuse-report'\n// The other three quarters of §512 (AGL-1983): the put-back procedure that\n// answers a notice, and the strike ledger that conditions the safe harbour.\nexport * from './dmca-counter-notice'\nexport * from './repeat-infringer'\n// `addBusinessDays` briefly existed TWICE — once here in './support-tiers'\n// and once in './dmca-counter-notice' — and two `export *` made the name\n// ambiguous, TS2308'ing this barrel and every tsconfig that reads it. The\n// interleave was fixed twice in parallel: once by naming a winner here, and\n// once by deleting the second definition so './dmca-counter-notice' imports\n// this one. Only the deletion survives, so the disambiguating re-export that\n// stood on this line is gone with it — `export *` is unambiguous again.\n//\n// The concern that argued for keeping two copies is worth answering rather\n// than dropping: a later tweak to the SUPPORT SLA window must never silently\n// move the §512(g)(2)(C) put-back deadline. It cannot. This function embeds\n// no window — it is \"add N business days\", and N is the argument. The two\n// windows live in their own constants (`SUPPORT_*` there,\n// `COUNTER_NOTICE_*_BUSINESS_DAYS` in the DMCA module) and never meet. What\n// SHOULD stay shared is the day arithmetic itself: two copies of a weekend\n// rule are two chances to disagree about which day a deadline falls on.\nexport * from './org-override-reason'\nexport * from './host-tokens'\nexport * from './variables'\n// What happened on a site that an automation can start on: the platform's\n// own event and the ones plugins declare (AGL-3080).\nexport * from './host-events'\nexport * from './expand-repeatables'\nexport * from './org-roles'\nexport * from './markdown-lite'\nexport * from './notifications'\n// The operator alert registry (AGL-3377); OPERATOR_ALERT_NOTIFICATION_TYPES\n// lives here now, derived from it.\nexport * from './operator-alerts'\nexport * from './definition-canvas-tree'\nexport * from './ensure-canvas-root'\nexport * from './repair-canvas-nodes'\nexport * from './detect-template-placeholders'\nexport * from './guarded-version-save'\nexport * from './version-draft'\nexport * from './local-storage-budget'\nexport * from './measure-node-map'\nexport * from './resolve-named-tokens'\nexport * from './screen-route'\nexport * from './host-naming'\nexport * from './name-search'\nexport * from './artifact-list-keys'\nexport * from './name-match'\nexport * from './api-plugins'\nexport * from './plugin-api-rate-limit'\n// The same-origin gate on visitor-facing plugin writes (AGL-1880). After\n// `plugin-api-rate-limit`, whose `isMachinePluginApiPath` it reuses so the\n// two gates exempt exactly the same machine surfaces.\nexport * from './plugin-api-cross-origin'\n// What a published page does when a visitor does something (AGL-3080): the\n// platform's interaction vocabulary, which the workflows plugin builds its\n// automations on.\nexport * from './site-interactions'\nexport * from './element-animation'\nexport * from './attribution-guard'\nexport * from './node-interactions'\nexport * from './element-ui'\nexport * from './image-upload-types'\nexport * from './sanitize-svg'\nexport * from './media-folders'\nexport * from './media-metadata'\nexport * from './media-ref'\nexport * from './rendered-widths'\n// After `media-ref`, whose reference grammar it reads stored URLs with.\nexport * from './paid-media-source'\nexport * from './author-css'\n// The isomorphic HTML rule (AGL-1901), after the CSS one it depends on.\nexport * from './author-html'\nexport * from './csv'\nexport * from './csv-upload'\nexport * from './artifact-provenance'\nexport * from './repeat-query'\nexport * from './plugin-manifest'\n// After `plugin-manifest`, whose revocation predicates it asks the kill\n// question with, and after `media-ref`, whose grammar decides what counts as a\n// first-party image.\nexport * from './email-starter-policy'\nexport * from './safe-json-ld'\n// Agent readiness (AGL-2716): content negotiation, the Markdown rendering of a\n// page, `/llms.txt` and the per-site OpenAPI document. All pure, all server-safe.\nexport * from './accept-negotiation'\nexport * from './agent-api-catalog'\nexport * from './agent-openapi'\nexport * from './author-html-markdown'\nexport * from './llms-txt'\nexport * from './page-markdown'\nexport * from './request-geo'\nexport * from './request-ip'\nexport * from './search-indexing'\n// Preview: a site or page only its team can see (AGL-4086).\nexport * from './site-preview'\nexport * from './seo-title'\nexport * from './sitemap'\nexport * from './visitor-consent'\nexport * from './social-image'\nexport * from './scope-tokens'\nexport * from './org-billing-doc'\nexport * from './stripe-deployment-mode'\nexport * from './deferred-image'\n"],"names":["csvCell","csvDocument","customImportTarget","customImportTargetKey","importNoMailServerSentence"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;CAMC,GACD,cAAc,6BAAyB;AACvC,cAAc,sBAAkB;AAChC,cAAc,0BAAsB;AACpC,yEAAyE;AACzE,uEAAuE;AACvE,cAAc,0BAAsB;AACpC,yEAAyE;AACzE,6EAA6E;AAC7E,4EAA4E;AAC5E,cAAc,oBAAgB;AAC9B,6EAA6E;AAC7E,2EAA2E;AAC3E,cAAc,gBAAY;AAC1B,cAAc,yBAAqB;AACnC,cAAc,0BAAsB;AACpC,cAAc,uBAAmB;AACjC,cAAc,uBAAmB;AACjC,cAAc,uBAAmB;AACjC,8EAA8E;AAC9E,8EAA8E;AAC9E,wEAAwE;AACxE,yEAAyE;AACzE,+EAA+E;AAC/E,+CAA+C;AAC/C,cAAc,4BAAwB;AACtC,cAAc,2BAAuB;AACrC,cAAc,8BAA0B;AACxC,cAAc,gBAAY;AAC1B,cAAc,sBAAkB;AAChC,cAAc,8BAA0B;AACxC,wEAAwE;AACxE,4EAA4E;AAC5E,cAAc,oBAAgB;AAC9B,8EAA8E;AAC9E,+EAA+E;AAC/E,0CAA0C;AAC1C,cAAc,iCAA6B;AAC3C,cAAc,sBAAkB;AAChC,sEAAsE;AACtE,uDAAuD;AACvD,cAAc,yBAAqB;AACnC,wEAAwE;AACxE,iEAAiE;AACjE,cAAc,uBAAmB;AACjC,cAAc,0BAAsB;AACpC,cAAc,gBAAY;AAC1B,0EAA0E;AAC1E,2EAA2E;AAC3E,cAAc,mBAAe;AAC7B,6EAA6E;AAC7E,4EAA4E;AAC5E,0EAA0E;AAC1E,cAAc,iBAAa;AAC3B,8EAA8E;AAC9E,6EAA6E;AAC7E,8EAA8E;AAC9E,2DAA2D;AAC3D,cAAc,WAAO;AACrB,yEAAyE;AACzE,0EAA0E;AAC1E,qEAAqE;AACrE,wCAAwC;AACxC,cAAc,2BAAuB;AACrC,0EAA0E;AAC1E,0EAA0E;AAC1E,4EAA4E;AAC5E,cAAc,sBAAkB;AAChC,sEAAsE;AACtE,2EAA2E;AAC3E,4EAA4E;AAC5E,SAAS;AACT,2EAA2E;AAC3E,4EAA4E;AAC5E,4EAA4E;AAC5E,0BAA0B;AAC1B,cAAc,kBAAc;AAC5B,cAAc,2BAAuB;AACrC,yEAAyE;AACzE,qEAAqE;AACrE,sEAAsE;AACtE,cAAc,4BAAwB;AACtC,2EAA2E;AAC3E,8EAA8E;AAC9E,2EAA2E;AAC3E,SAAS;AACT,SACEA,OAAO,EACPC,WAAW,EACXC,kBAAkB,EAClBC,qBAAqB,EACrBC,0BAA0B,QACrB,kBAAc;AAMrB,2EAA2E;AAC3E,6EAA6E;AAC7E,6EAA6E;AAC7E,cAAc,6BAAyB;AACvC,+EAA+E;AAC/E,wEAAwE;AACxE,+EAA+E;AAC/E,cAAc;AACd,cAAc,sBAAkB;AAChC,cAAc,yBAAqB;AACnC,2EAA2E;AAC3E,yDAAyD;AACzD,cAAc,yBAAqB;AACnC,2EAA2E;AAC3E,4EAA4E;AAC5E,wEAAwE;AACxE,6EAA6E;AAC7E,2EAA2E;AAC3E,cAAc,wBAAoB;AAClC,mFAAmF;AACnF,2EAA2E;AAC3E,4EAA4E;AAC5E,0EAA0E;AAC1E,kCAAkC;AAClC,6EAA6E;AAC7E,8EAA8E;AAC9E,cAAc,yBAAqB;AACnC,+DAA+D;AAC/D,8EAA8E;AAC9E,4EAA4E;AAC5E,4EAA4E;AAC5E,yEAAyE;AACzE,cAAc,2BAAuB;AACrC,cAAc,4BAAwB;AACtC,0DAA0D;AAC1D,cAAc,4BAAwB;AACtC,qEAAqE;AACrE,cAAc,8BAA0B;AACxC,cAAc,yBAAqB;AACnC,cAAc,iBAAa;AAC3B,cAAc,mCAA+B;AAC7C,qEAAqE;AACrE,cAAc,+BAA2B;AACzC,cAAc,gBAAY;AAC1B,cAAc,qBAAiB;AAC/B,cAAc,kBAAc;AAC5B,cAAc,iBAAa;AAC3B,oDAAoD;AACpD,cAAc,4BAAwB;AACtC,cAAc,2BAAuB;AACrC,cAAc,kBAAc;AAC5B,cAAc,oBAAgB;AAC9B,cAAc,uBAAmB;AACjC,cAAc,qBAAiB;AAC/B,cAAc,uBAAmB;AACjC,6EAA6E;AAC7E,yEAAyE;AACzE,iBAAiB;AACjB,cAAc,wBAAoB;AAClC,cAAc,uBAAmB;AACjC,cAAc,mBAAe;AAC7B,cAAc,4BAAwB;AACtC,6EAA6E;AAC7E,+CAA+C;AAC/C,cAAc,wBAAoB;AAClC,wEAAwE;AACxE,0EAA0E;AAC1E,4DAA4D;AAC5D,cAAc,uBAAmB;AACjC,4EAA4E;AAC5E,0EAA0E;AAC1E,2EAA2E;AAC3E,0EAA0E;AAC1E,oBAAoB;AACpB,cAAc,4BAAwB;AACtC,cAAc,wBAAoB;AAClC,+EAA+E;AAC/E,+EAA+E;AAC/E,4EAA4E;AAC5E,cAAc,mBAAe;AAC7B,6EAA6E;AAC7E,0EAA0E;AAC1E,iBAAiB;AACjB,cAAc,iBAAa;AAC3B,cAAc,sBAAkB;AAChC,cAAc,yBAAqB;AACnC,iEAAiE;AACjE,6DAA6D;AAC7D,cAAc,qBAAiB;AAC/B,4EAA4E;AAC5E,cAAc,uBAAmB;AACjC,6EAA6E;AAC7E,cAAc,iBAAa;AAC3B,cAAc,aAAS;AACvB,yEAAyE;AACzE,kEAAkE;AAClE,cAAc,qBAAiB;AAC/B,cAAc,8BAA0B;AACxC,cAAc,qBAAiB;AAC/B,gEAAgE;AAChE,4EAA4E;AAC5E,cAAc,wBAAoB;AAClC,cAAc,qBAAiB;AAC/B,yEAAyE;AACzE,2DAA2D;AAC3D,cAAc,iBAAa;AAC3B,cAAc,6BAAyB;AACvC,cAAc,2BAAuB;AACrC,2EAA2E;AAC3E,cAAc,yBAAqB;AACnC,cAAc,qBAAiB;AAC/B,yEAAyE;AACzE,6EAA6E;AAC7E,mCAAmC;AACnC,cAAc,yBAAqB;AACnC,cAAc,gBAAY;AAC1B,cAAc,wBAAoB;AAClC,cAAc,4BAAwB;AACtC,cAAc,yBAAqB;AACnC,cAAc,oBAAgB;AAC9B,2EAA2E;AAC3E,4EAA4E;AAC5E,cAAc,2BAAuB;AACrC,cAAc,wBAAoB;AAClC,2EAA2E;AAC3E,yEAAyE;AACzE,0EAA0E;AAC1E,4EAA4E;AAC5E,4EAA4E;AAC5E,6EAA6E;AAC7E,wEAAwE;AACxE,EAAE;AACF,2EAA2E;AAC3E,6EAA6E;AAC7E,4EAA4E;AAC5E,0EAA0E;AAC1E,0DAA0D;AAC1D,4EAA4E;AAC5E,2EAA2E;AAC3E,wEAAwE;AACxE,cAAc,2BAAuB;AACrC,cAAc,mBAAe;AAC7B,cAAc,iBAAa;AAC3B,0EAA0E;AAC1E,qDAAqD;AACrD,cAAc,mBAAe;AAC7B,cAAc,0BAAsB;AACpC,cAAc,iBAAa;AAC3B,cAAc,qBAAiB;AAC/B,cAAc,qBAAiB;AAC/B,4EAA4E;AAC5E,mCAAmC;AACnC,cAAc,uBAAmB;AACjC,cAAc,8BAA0B;AACxC,cAAc,0BAAsB;AACpC,cAAc,2BAAuB;AACrC,cAAc,oCAAgC;AAC9C,cAAc,4BAAwB;AACtC,cAAc,qBAAiB;AAC/B,cAAc,4BAAwB;AACtC,cAAc,wBAAoB;AAClC,cAAc,4BAAwB;AACtC,cAAc,oBAAgB;AAC9B,cAAc,mBAAe;AAC7B,cAAc,mBAAe;AAC7B,cAAc,0BAAsB;AACpC,cAAc,kBAAc;AAC5B,cAAc,mBAAe;AAC7B,cAAc,6BAAyB;AACvC,yEAAyE;AACzE,2EAA2E;AAC3E,sDAAsD;AACtD,cAAc,+BAA2B;AACzC,2EAA2E;AAC3E,2EAA2E;AAC3E,kBAAkB;AAClB,cAAc,yBAAqB;AACnC,cAAc,yBAAqB;AACnC,cAAc,yBAAqB;AACnC,cAAc,yBAAqB;AACnC,cAAc,kBAAc;AAC5B,cAAc,0BAAsB;AACpC,cAAc,oBAAgB;AAC9B,cAAc,qBAAiB;AAC/B,cAAc,sBAAkB;AAChC,cAAc,iBAAa;AAC3B,cAAc,uBAAmB;AACjC,wEAAwE;AACxE,cAAc,yBAAqB;AACnC,cAAc,kBAAc;AAC5B,wEAAwE;AACxE,cAAc,mBAAe;AAC7B,cAAc,WAAO;AACrB,cAAc,kBAAc;AAC5B,cAAc,2BAAuB;AACrC,cAAc,oBAAgB;AAC9B,cAAc,uBAAmB;AACjC,wEAAwE;AACxE,+EAA+E;AAC/E,qBAAqB;AACrB,cAAc,4BAAwB;AACtC,cAAc,oBAAgB;AAC9B,+EAA+E;AAC/E,kFAAkF;AAClF,cAAc,0BAAsB;AACpC,cAAc,yBAAqB;AACnC,cAAc,qBAAiB;AAC/B,cAAc,4BAAwB;AACtC,cAAc,gBAAY;AAC1B,cAAc,qBAAiB;AAC/B,cAAc,mBAAe;AAC7B,cAAc,kBAAc;AAC5B,cAAc,uBAAmB;AACjC,4DAA4D;AAC5D,cAAc,oBAAgB;AAC9B,cAAc,iBAAa;AAC3B,cAAc,eAAW;AACzB,cAAc,uBAAmB;AACjC,cAAc,oBAAgB;AAC9B,cAAc,oBAAgB;AAC9B,cAAc,uBAAmB;AACjC,cAAc,8BAA0B;AACxC,cAAc,sBAAkB"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Where a version's WORKING DRAFT lives (AGL-1152): one document per version,
|
|
19
|
+
* `…/versions/{versionId}/draft/current`, on every plan. The Besigner saves
|
|
20
|
+
* to it as an author works and offers it with Open draft when the page is
|
|
21
|
+
* opened; publishing moves it into the version and clears it.
|
|
22
|
+
*
|
|
23
|
+
* Held here, in the core, because two writers must agree on it to the
|
|
24
|
+
* letter: the Besigner (`besigner-server-draft.ts`) and an AI edit job, which
|
|
25
|
+
* saves its change into the same draft so the author reviews and publishes
|
|
26
|
+
* it in the Besigner exactly as they would their own (AGL-4116).
|
|
27
|
+
*/
|
|
28
|
+
/** The subcollection under a version that holds its draft. */
|
|
29
|
+
export declare const VERSION_DRAFT_COLLECTION = "draft";
|
|
30
|
+
/** The only id a draft may have. One per version, by construction. */
|
|
31
|
+
export declare const VERSION_DRAFT_DOC_ID = "current";
|
|
32
|
+
/**
|
|
33
|
+
* The query a link into the Besigner carries to open the version's saved
|
|
34
|
+
* draft on arrival (AGL-4116), rather than leave it behind the Open draft
|
|
35
|
+
* banner. An AI edit's Open in the Besigner uses it: the person asked for the
|
|
36
|
+
* change, so the change is what they should land on.
|
|
37
|
+
*/
|
|
38
|
+
export declare const BESIGNER_OPEN_DRAFT_PARAM = "draft";
|
|
39
|
+
/** The value of {@link BESIGNER_OPEN_DRAFT_PARAM} that asks for the draft to open. */
|
|
40
|
+
export declare const BESIGNER_OPEN_DRAFT_VALUE = "open";
|
|
41
|
+
/** A Besigner address that opens the version's saved draft on arrival. */
|
|
42
|
+
export declare function besignerOpenDraftHref(href: string): string;
|
|
43
|
+
/** The version segment of a document with no versions of its own, as the mirror writes it. */
|
|
44
|
+
export declare const COEDIT_MIRROR_CURRENT_VERSION = "current";
|
|
45
|
+
/**
|
|
46
|
+
* The co-edit mirror room of one version (AGL-677): the unsaved canvas work
|
|
47
|
+
* a Besigner tab publishes for the others in the room, replayed to whoever
|
|
48
|
+
* joins until a save clears it. `apps/console/hooks/use-coediting.ts` builds
|
|
49
|
+
* the same path, and `presence-version-scope.spec.ts` holds the two together.
|
|
50
|
+
*
|
|
51
|
+
* Saving makes the stored document authoritative, so a save clears the
|
|
52
|
+
* room; an AI edit job that saves into a version's draft clears it too
|
|
53
|
+
* (AGL-4116), or the older unsaved work replays over the draft it just wrote.
|
|
54
|
+
*/
|
|
55
|
+
export declare function coeditMirrorRoomPath(input: {
|
|
56
|
+
orgId: string;
|
|
57
|
+
hostId: string;
|
|
58
|
+
docType: string;
|
|
59
|
+
docId: string;
|
|
60
|
+
versionId?: string | null;
|
|
61
|
+
}): string;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ /**
|
|
17
|
+
* Where a version's WORKING DRAFT lives (AGL-1152): one document per version,
|
|
18
|
+
* `…/versions/{versionId}/draft/current`, on every plan. The Besigner saves
|
|
19
|
+
* to it as an author works and offers it with Open draft when the page is
|
|
20
|
+
* opened; publishing moves it into the version and clears it.
|
|
21
|
+
*
|
|
22
|
+
* Held here, in the core, because two writers must agree on it to the
|
|
23
|
+
* letter: the Besigner (`besigner-server-draft.ts`) and an AI edit job, which
|
|
24
|
+
* saves its change into the same draft so the author reviews and publishes
|
|
25
|
+
* it in the Besigner exactly as they would their own (AGL-4116).
|
|
26
|
+
*/ /** The subcollection under a version that holds its draft. */ export const VERSION_DRAFT_COLLECTION = 'draft';
|
|
27
|
+
/** The only id a draft may have. One per version, by construction. */ export const VERSION_DRAFT_DOC_ID = 'current';
|
|
28
|
+
/**
|
|
29
|
+
* The query a link into the Besigner carries to open the version's saved
|
|
30
|
+
* draft on arrival (AGL-4116), rather than leave it behind the Open draft
|
|
31
|
+
* banner. An AI edit's Open in the Besigner uses it: the person asked for the
|
|
32
|
+
* change, so the change is what they should land on.
|
|
33
|
+
*/ export const BESIGNER_OPEN_DRAFT_PARAM = 'draft';
|
|
34
|
+
/** The value of {@link BESIGNER_OPEN_DRAFT_PARAM} that asks for the draft to open. */ export const BESIGNER_OPEN_DRAFT_VALUE = 'open';
|
|
35
|
+
/** A Besigner address that opens the version's saved draft on arrival. */ export function besignerOpenDraftHref(href) {
|
|
36
|
+
return `${href}?${BESIGNER_OPEN_DRAFT_PARAM}=${BESIGNER_OPEN_DRAFT_VALUE}`;
|
|
37
|
+
}
|
|
38
|
+
/** The version segment of a document with no versions of its own, as the mirror writes it. */ export const COEDIT_MIRROR_CURRENT_VERSION = 'current';
|
|
39
|
+
/**
|
|
40
|
+
* The co-edit mirror room of one version (AGL-677): the unsaved canvas work
|
|
41
|
+
* a Besigner tab publishes for the others in the room, replayed to whoever
|
|
42
|
+
* joins until a save clears it. `apps/console/hooks/use-coediting.ts` builds
|
|
43
|
+
* the same path, and `presence-version-scope.spec.ts` holds the two together.
|
|
44
|
+
*
|
|
45
|
+
* Saving makes the stored document authoritative, so a save clears the
|
|
46
|
+
* room; an AI edit job that saves into a version's draft clears it too
|
|
47
|
+
* (AGL-4116), or the older unsaved work replays over the draft it just wrote.
|
|
48
|
+
*/ export function coeditMirrorRoomPath(input) {
|
|
49
|
+
var _input_versionId;
|
|
50
|
+
return `coedit/${input.orgId}/${input.hostId}/${input.docType}/${input.docId}/${(_input_versionId = input.versionId) != null ? _input_versionId : COEDIT_MIRROR_CURRENT_VERSION}/nodes`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
//# sourceMappingURL=version-draft.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/version-draft.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Where a version's WORKING DRAFT lives (AGL-1152): one document per version,\n * `…/versions/{versionId}/draft/current`, on every plan. The Besigner saves\n * to it as an author works and offers it with Open draft when the page is\n * opened; publishing moves it into the version and clears it.\n *\n * Held here, in the core, because two writers must agree on it to the\n * letter: the Besigner (`besigner-server-draft.ts`) and an AI edit job, which\n * saves its change into the same draft so the author reviews and publishes\n * it in the Besigner exactly as they would their own (AGL-4116).\n */\n\n/** The subcollection under a version that holds its draft. */\nexport const VERSION_DRAFT_COLLECTION = 'draft'\n\n/** The only id a draft may have. One per version, by construction. */\nexport const VERSION_DRAFT_DOC_ID = 'current'\n\n/**\n * The query a link into the Besigner carries to open the version's saved\n * draft on arrival (AGL-4116), rather than leave it behind the Open draft\n * banner. An AI edit's Open in the Besigner uses it: the person asked for the\n * change, so the change is what they should land on.\n */\nexport const BESIGNER_OPEN_DRAFT_PARAM = 'draft'\n\n/** The value of {@link BESIGNER_OPEN_DRAFT_PARAM} that asks for the draft to open. */\nexport const BESIGNER_OPEN_DRAFT_VALUE = 'open'\n\n/** A Besigner address that opens the version's saved draft on arrival. */\nexport function besignerOpenDraftHref(href: string): string {\n return `${href}?${BESIGNER_OPEN_DRAFT_PARAM}=${BESIGNER_OPEN_DRAFT_VALUE}`\n}\n\n/** The version segment of a document with no versions of its own, as the mirror writes it. */\nexport const COEDIT_MIRROR_CURRENT_VERSION = 'current'\n\n/**\n * The co-edit mirror room of one version (AGL-677): the unsaved canvas work\n * a Besigner tab publishes for the others in the room, replayed to whoever\n * joins until a save clears it. `apps/console/hooks/use-coediting.ts` builds\n * the same path, and `presence-version-scope.spec.ts` holds the two together.\n *\n * Saving makes the stored document authoritative, so a save clears the\n * room; an AI edit job that saves into a version's draft clears it too\n * (AGL-4116), or the older unsaved work replays over the draft it just wrote.\n */\nexport function coeditMirrorRoomPath(input: {\n orgId: string\n hostId: string\n docType: string\n docId: string\n versionId?: string | null\n}): string {\n return `coedit/${input.orgId}/${input.hostId}/${input.docType}/${input.docId}/${input.versionId ?? COEDIT_MIRROR_CURRENT_VERSION}/nodes`\n}\n"],"names":["VERSION_DRAFT_COLLECTION","VERSION_DRAFT_DOC_ID","BESIGNER_OPEN_DRAFT_PARAM","BESIGNER_OPEN_DRAFT_VALUE","besignerOpenDraftHref","href","COEDIT_MIRROR_CURRENT_VERSION","coeditMirrorRoomPath","input","orgId","hostId","docType","docId","versionId"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;CAUC,GAED,4DAA4D,GAC5D,OAAO,MAAMA,2BAA2B,QAAO;AAE/C,oEAAoE,GACpE,OAAO,MAAMC,uBAAuB,UAAS;AAE7C;;;;;CAKC,GACD,OAAO,MAAMC,4BAA4B,QAAO;AAEhD,oFAAoF,GACpF,OAAO,MAAMC,4BAA4B,OAAM;AAE/C,wEAAwE,GACxE,OAAO,SAASC,sBAAsBC,IAAY;IAChD,OAAO,GAAGA,KAAK,CAAC,EAAEH,0BAA0B,CAAC,EAAEC,2BAA2B;AAC5E;AAEA,4FAA4F,GAC5F,OAAO,MAAMG,gCAAgC,UAAS;AAEtD;;;;;;;;;CASC,GACD,OAAO,SAASC,qBAAqBC,KAMpC;QACiFA;IAAhF,OAAO,CAAC,OAAO,EAAEA,MAAMC,KAAK,CAAC,CAAC,EAAED,MAAME,MAAM,CAAC,CAAC,EAAEF,MAAMG,OAAO,CAAC,CAAC,EAAEH,MAAMI,KAAK,CAAC,CAAC,GAAEJ,mBAAAA,MAAMK,SAAS,YAAfL,mBAAmBF,8BAA8B,MAAM,CAAC;AAC1I"}
|
|
@@ -76,6 +76,20 @@ export interface PluginJobHostGate {
|
|
|
76
76
|
* shut by infrastructure it never asked for. The loss is real and is why
|
|
77
77
|
* `hasPluginJobHostLockdown` is reported by the runner route rather than
|
|
78
78
|
* left to be inferred from nothing happening.
|
|
79
|
+
*
|
|
80
|
+
* ONE VERDICT PER HOST PER GATE (AGL-4121). The resolver's answer costs three
|
|
81
|
+
* Firestore reads (org id, org, host) and outside a request `React.cache`
|
|
82
|
+
* dedupes none of them, so a scan that asked once per row paid three reads per
|
|
83
|
+
* row for one site's answer. The runner mints a gate per job per run, so a
|
|
84
|
+
* verdict held here lives exactly as long as the run that asked, the way the
|
|
85
|
+
* sending-identity and branding maps in those scans do, and the next beat asks
|
|
86
|
+
* afresh. A lock laid mid-run therefore reaches a site already asked about on
|
|
87
|
+
* the next beat rather than on that run's next row.
|
|
88
|
+
*
|
|
89
|
+
* The promise is what is held, so askers running side by side
|
|
90
|
+
* (`pos-cellular-billing` asks under a `Promise.all`) share one read. A
|
|
91
|
+
* resolver that failed gave no verdict, so nothing is kept and the next row
|
|
92
|
+
* asks again, as it always has.
|
|
79
93
|
*/
|
|
80
94
|
export declare function pluginJobHostGate(): PluginJobHostGate;
|
|
81
95
|
/** Test seam: let the "warned once" latch be re-armed. */
|
|
@@ -100,15 +100,38 @@
|
|
|
100
100
|
* shut by infrastructure it never asked for. The loss is real and is why
|
|
101
101
|
* `hasPluginJobHostLockdown` is reported by the runner route rather than
|
|
102
102
|
* left to be inferred from nothing happening.
|
|
103
|
+
*
|
|
104
|
+
* ONE VERDICT PER HOST PER GATE (AGL-4121). The resolver's answer costs three
|
|
105
|
+
* Firestore reads (org id, org, host) and outside a request `React.cache`
|
|
106
|
+
* dedupes none of them, so a scan that asked once per row paid three reads per
|
|
107
|
+
* row for one site's answer. The runner mints a gate per job per run, so a
|
|
108
|
+
* verdict held here lives exactly as long as the run that asked, the way the
|
|
109
|
+
* sending-identity and branding maps in those scans do, and the next beat asks
|
|
110
|
+
* afresh. A lock laid mid-run therefore reaches a site already asked about on
|
|
111
|
+
* the next beat rather than on that run's next row.
|
|
112
|
+
*
|
|
113
|
+
* The promise is what is held, so askers running side by side
|
|
114
|
+
* (`pos-cellular-billing` asks under a `Promise.all`) share one read. A
|
|
115
|
+
* resolver that failed gave no verdict, so nothing is kept and the next row
|
|
116
|
+
* asks again, as it always has.
|
|
103
117
|
*/ export function pluginJobHostGate() {
|
|
118
|
+
const verdicts = new Map();
|
|
119
|
+
const ask = async (hostId)=>{
|
|
120
|
+
if (!hostId) return false;
|
|
121
|
+
if (!hostLockedResolver) {
|
|
122
|
+
warnMissingResolver();
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
return await hostLockedResolver(hostId) === true;
|
|
126
|
+
};
|
|
104
127
|
return {
|
|
105
|
-
isLocked:
|
|
106
|
-
|
|
107
|
-
if (
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
return
|
|
128
|
+
isLocked: (hostId)=>{
|
|
129
|
+
const held = verdicts.get(hostId);
|
|
130
|
+
if (held) return held;
|
|
131
|
+
const verdict = ask(hostId);
|
|
132
|
+
verdicts.set(hostId, verdict);
|
|
133
|
+
verdict.catch(()=>verdicts.delete(hostId));
|
|
134
|
+
return verdict;
|
|
112
135
|
}
|
|
113
136
|
};
|
|
114
137
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-jobs.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Plugin scheduled jobs (AGL-435, Strapi cron parity): `/server` entries\n * register named jobs with an interval; a guarded platform runner route\n * (`/api/plugins/run-jobs`, secret header, invoked by the deployment's\n * scheduler) executes the due ones. The REGISTRY is pure — due-ness and\n * last-run persistence belong to the runner, so core stays storage-free.\n * Handlers must be idempotent and bounded (they share the API process).\n */\n\n/*==========================================\n * THE LOCKDOWN GATE FOR BACKGROUND WORK (AGL-2495, from the AGL-1621 drill).\n *\n * A job runs on PLATFORM credentials, from a secret-gated route, with no\n * visitor, no session and no org — so none of the gates that cover the\n * visitor-facing write paths are anywhere near it. That is exactly the shape\n * the drill found in `publish-schedule-job.ts`: a scheduled publish firing on\n * a locked host. The commerce and bookings beats are the sharper version of\n * the same thing, because they touch orders, stock and money for a host that\n * may be suspended for abuse, non-payment or a legal takedown.\n *\n * ## Why the gate lives HERE and not at the six call sites\n *\n * Six call-site edits close six holes and guarantee the seventh is forgotten.\n * Gating in the job contract means the seventh job is covered the day it is\n * written: `lockdown` below is REQUIRED, so a new `registerPluginJob({…})`\n * does not compile until its author has answered \"what does this touch\".\n *\n * (The nx boundary is NOT why. `libs/plugins/*` CAN import\n * `@aglyn/tenant-data-admin` — it carries `scope:data` and `scope:aglyn`,\n * both on the `aglyn:addons` allowlist, and 196 files in `libs/plugins`\n * already do. The AGL-2495 sweep recorded the opposite as its reason for\n * deferring; it was wrong about the constraint and right about the design.)\n *\n * ## Why core cannot just call `getSiteLockdown` itself\n *\n * `@aglyn/tenant-data-admin` imports `@aglyn/aglyn/server`, so a core import\n * of it is a project CYCLE. Hence the same registry shape the platform uses\n * for every other app↔core edge (`registerBillingWebhookHandler`,\n * `registerSitePageResolver`): the host app\n * registers the resolver from a place that may import the admin lib, and core\n * looks it up. Nothing is imported statically in either direction and a\n * self-host build with a different carrier registers its own.\n *\n * ## Fail-open is INHERITED, not re-decided\n *\n * The gate asks whatever the app registered — on the tenant that is\n * `getSiteLockdown`, which owns the fail-open/fail-closed decision (and the\n * takedown-class ratchet on top of it). This module deliberately holds no\n * parallel notion of what a lock is: it has one question and no opinion.\n *=========================================*/\n\n/**\n * The one question a beat asks before touching a host: is a lock active that\n * forbids this write? Resolved by the host app, never by core.\n */\nexport type PluginJobHostLockedResolver = (\n hostId: string,\n) => Promise<boolean> | boolean\n\nlet hostLockedResolver: PluginJobHostLockedResolver | null = null\n\n/**\n * Registers the platform's per-host lockdown verdict for background work,\n * from a place that may import the admin data lib — on this platform,\n * `apps/tenant/utils/plugin-job-lockdown.ts`, loaded for its side effect by\n * the runner route beside the core job registrations.\n *\n * Idempotent and last-registration-wins, unlike a registry that holds a\n * capability with a plugin OWNER, where a second claimant is a\n * misconfiguration. This is infrastructure the deployment supplies exactly\n * once, and a process that loads the module twice must not throw on the\n * second pass.\n */\nexport function registerPluginJobHostLockdown(\n resolver: PluginJobHostLockedResolver,\n): void {\n hostLockedResolver = resolver\n}\n\n/** Has the deployment wired a verdict? Reported by the runner route. */\nexport function hasPluginJobHostLockdown(): boolean {\n return hostLockedResolver !== null\n}\n\n/** Test seam: forget the registration. */\nexport function resetPluginJobHostLockdownForTests(): void {\n hostLockedResolver = null\n}\n\n/**\n * The gate handed to a `per-host` job. One method, so there is exactly one\n * thing to call and one thing for the coverage guard to look for:\n *\n * if (await gate.isLocked(hostId)) continue\n *\n * SKIPPED, NOT DROPPED is the caller's obligation and the reason this\n * answers a question rather than performing the skip: `continue` leaves the\n * row exactly as it was, so the work lands on the next beat after the lift.\n * A gate that swallowed the row — stamping it done, deleting it, advancing a\n * cursor past it — would turn a pause into a cancellation, which is what\n * `publish-schedule-job.ts` argued at length and what every job here copies.\n */\nexport interface PluginJobHostGate {\n isLocked(hostId: string): Promise<boolean>\n}\n\n/**\n * The gate as the runner builds it.\n *\n * The MANUAL doors — `commerce/process-abandoned`, `commerce/process-restock`,\n * `bookings/reminders` — do NOT use it (AGL-3356). They are served by the\n * console, which registers no resolver, so this answered \"not locked\" for\n * every site there and a forced pass mailed a suspended site's customers.\n * They take `siteLockdownJobGate()` from the admin lib instead: the same\n * function the tenant registers here, needing no registration at all.\n *\n * NO resolver registered answers \"not locked\", loudly and once. That is the\n * fail-open direction on purpose: a deployment that has not wired a carrier\n * (a self-host, a test process) must not have every background job welded\n * shut by infrastructure it never asked for. The loss is real and is why\n * `hasPluginJobHostLockdown` is reported by the runner route rather than\n * left to be inferred from nothing happening.\n */\nexport function pluginJobHostGate(): PluginJobHostGate {\n return {\n isLocked: async (hostId: string) => {\n if (!hostId) return false\n if (!hostLockedResolver) {\n warnMissingResolver()\n return false\n }\n return (await hostLockedResolver(hostId)) === true\n },\n }\n}\n\nlet warnedMissingResolver = false\nfunction warnMissingResolver(): void {\n if (warnedMissingResolver) return\n warnedMissingResolver = true\n console.error(\n '[plugin-jobs] no host lockdown resolver is registered — background ' +\n 'jobs are running UNGATED. Register one with ' +\n 'registerPluginJobHostLockdown() from the app that owns the runner.',\n )\n}\n\n/** Test seam: let the \"warned once\" latch be re-armed. */\nexport function resetPluginJobLockdownWarningForTests(): void {\n warnedMissingResolver = false\n}\n\n/**\n * What a job touches, and therefore what the runner owes it. REQUIRED on\n * every registration — the point of the field is that a new job cannot\n * compile without its author answering.\n */\nexport type PluginJobLockdown =\n /**\n * The job acts FOR HOSTS. The runner injects a {@link PluginJobHostGate}\n * and the handler must ask it for every host it is about to touch.\n */\n | { scope: 'per-host' }\n /**\n * The job touches nothing a lock could be about. `reason` is the argument,\n * and `lockdown-tenant-api-coverage.spec.ts` holds the set by name so a new\n * one has to be argued rather than declared.\n */\n | { scope: 'platform'; reason: string }\n\nexport interface PluginJob {\n pluginId: string\n /** Stable job name, unique within the plugin ('expire-stale-holds'). */\n name: string\n intervalMinutes: number\n description?: string\n /** See {@link PluginJobLockdown}. Required, deliberately. */\n lockdown: PluginJobLockdown\n handler: (gate: PluginJobHostGate) => Promise<void> | void\n}\n\nconst jobs = new Map<string, PluginJob>()\n\nexport const pluginJobKey = (job: Pick<PluginJob, 'pluginId' | 'name'>) =>\n `${job.pluginId}:${job.name}`\n\n/** Idempotent per pluginId:name. */\nexport function registerPluginJob(job: PluginJob): void {\n jobs.set(pluginJobKey(job), job)\n}\n\nexport function listPluginJobs(): PluginJob[] {\n return [...jobs.values()]\n}\n\nexport interface PluginJobResult {\n key: string\n ok: boolean\n error?: string\n}\n\n/**\n * The gate a `platform`-scoped job gets: one that REFUSES to answer.\n *\n * A declaration of `platform` scope is a claim that there is no host to ask\n * about. If such a job asks anyway, the claim is false and the registration\n * is the thing that needs fixing — so the runner surfaces it as that job's\n * own failure (isolated, like every other job error) instead of quietly\n * answering \"not locked\" and letting a mislabelled job mutate a locked site.\n *\n * This is the one place the contract is enforced at RUNTIME rather than by\n * the type or by the guard, and it is enforceable precisely because asking\n * is the only way to get an answer.\n */\nfunction refusingGate(key: string): PluginJobHostGate {\n return {\n isLocked: async (hostId: string) => {\n throw new Error(\n `plugin job ${key} declared lockdown scope 'platform' but asked ` +\n `about host \"${hostId}\". Declare { scope: 'per-host' } instead.`,\n )\n },\n }\n}\n\n/**\n * Runs the jobs `due` selects (default: all), sequentially with error\n * isolation — one broken job never blocks the rest.\n *\n * Every handler is handed a {@link PluginJobHostGate} (AGL-2495). It is a\n * parameter rather than something the handler imports so that the runner\n * stays the single place the verdict comes from: swap what\n * `registerPluginJobHostLockdown` holds and every job on the beat changes\n * behaviour together, including one written next month.\n */\nexport async function runPluginJobs(\n due?: (job: PluginJob) => boolean,\n): Promise<PluginJobResult[]> {\n const results: PluginJobResult[] = []\n for (const job of jobs.values()) {\n if (due && !due(job)) continue\n const key = pluginJobKey(job)\n try {\n await job.handler(\n job.lockdown?.scope === 'platform'\n ? refusingGate(key)\n : pluginJobHostGate(),\n )\n results.push({ key, ok: true })\n } catch (error) {\n console.error(`plugin job ${key} failed:`, error)\n results.push({ key, ok: false, error: String(error) })\n }\n }\n return results\n}\n"],"names":["hostLockedResolver","registerPluginJobHostLockdown","resolver","hasPluginJobHostLockdown","resetPluginJobHostLockdownForTests","pluginJobHostGate","isLocked","hostId","warnMissingResolver","warnedMissingResolver","console","error","resetPluginJobLockdownWarningForTests","jobs","Map","pluginJobKey","job","pluginId","name","registerPluginJob","set","listPluginJobs","values","refusingGate","key","Error","runPluginJobs","due","results","handler","lockdown","scope","push","ok","String"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;CAOC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2CAwC2C,GAE3C;;;CAGC,GAKD,IAAIA,qBAAyD;AAE7D;;;;;;;;;;;CAWC,GACD,OAAO,SAASC,8BACdC,QAAqC;IAErCF,qBAAqBE;AACvB;AAEA,sEAAsE,GACtE,OAAO,SAASC;IACd,OAAOH,uBAAuB;AAChC;AAEA,wCAAwC,GACxC,OAAO,SAASI;IACdJ,qBAAqB;AACvB;AAmBA;;;;;;;;;;;;;;;;CAgBC,GACD,OAAO,SAASK;IACd,OAAO;QACLC,UAAU,OAAOC;YACf,IAAI,CAACA,QAAQ,OAAO;YACpB,IAAI,CAACP,oBAAoB;gBACvBQ;gBACA,OAAO;YACT;YACA,OAAO,AAAC,MAAMR,mBAAmBO,YAAa;QAChD;IACF;AACF;AAEA,IAAIE,wBAAwB;AAC5B,SAASD;IACP,IAAIC,uBAAuB;IAC3BA,wBAAwB;IACxBC,QAAQC,KAAK,CACX,wEACE,iDACA;AAEN;AAEA,wDAAwD,GACxD,OAAO,SAASC;IACdH,wBAAwB;AAC1B;AA+BA,MAAMI,OAAO,IAAIC;AAEjB,OAAO,MAAMC,eAAe,CAACC,MAC3B,GAAGA,IAAIC,QAAQ,CAAC,CAAC,EAAED,IAAIE,IAAI,EAAE,CAAA;AAE/B,kCAAkC,GAClC,OAAO,SAASC,kBAAkBH,GAAc;IAC9CH,KAAKO,GAAG,CAACL,aAAaC,MAAMA;AAC9B;AAEA,OAAO,SAASK;IACd,OAAO;WAAIR,KAAKS,MAAM;KAAG;AAC3B;AAQA;;;;;;;;;;;;CAYC,GACD,SAASC,aAAaC,GAAW;IAC/B,OAAO;QACLlB,UAAU,OAAOC;YACf,MAAM,IAAIkB,MACR,CAAC,WAAW,EAAED,IAAI,8CAA8C,CAAC,GAC/D,CAAC,YAAY,EAAEjB,OAAO,yCAAyC,CAAC;QAEtE;IACF;AACF;AAEA;;;;;;;;;CASC,GACD,OAAO,eAAemB,cACpBC,GAAiC;IAEjC,MAAMC,UAA6B,EAAE;IACrC,KAAK,MAAMZ,OAAOH,KAAKS,MAAM,GAAI;QAC/B,IAAIK,OAAO,CAACA,IAAIX,MAAM;QACtB,MAAMQ,MAAMT,aAAaC;QACzB,IAAI;gBAEAA;YADF,MAAMA,IAAIa,OAAO,CACfb,EAAAA,gBAAAA,IAAIc,QAAQ,qBAAZd,cAAce,KAAK,MAAK,aACpBR,aAAaC,OACbnB;YAENuB,QAAQI,IAAI,CAAC;gBAAER;gBAAKS,IAAI;YAAK;QAC/B,EAAE,OAAOtB,OAAO;YACdD,QAAQC,KAAK,CAAC,CAAC,WAAW,EAAEa,IAAI,QAAQ,CAAC,EAAEb;YAC3CiB,QAAQI,IAAI,CAAC;gBAAER;gBAAKS,IAAI;gBAAOtB,OAAOuB,OAAOvB;YAAO;QACtD;IACF;IACA,OAAOiB;AACT"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-jobs.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Plugin scheduled jobs (AGL-435, Strapi cron parity): `/server` entries\n * register named jobs with an interval; a guarded platform runner route\n * (`/api/plugins/run-jobs`, secret header, invoked by the deployment's\n * scheduler) executes the due ones. The REGISTRY is pure — due-ness and\n * last-run persistence belong to the runner, so core stays storage-free.\n * Handlers must be idempotent and bounded (they share the API process).\n */\n\n/*==========================================\n * THE LOCKDOWN GATE FOR BACKGROUND WORK (AGL-2495, from the AGL-1621 drill).\n *\n * A job runs on PLATFORM credentials, from a secret-gated route, with no\n * visitor, no session and no org — so none of the gates that cover the\n * visitor-facing write paths are anywhere near it. That is exactly the shape\n * the drill found in `publish-schedule-job.ts`: a scheduled publish firing on\n * a locked host. The commerce and bookings beats are the sharper version of\n * the same thing, because they touch orders, stock and money for a host that\n * may be suspended for abuse, non-payment or a legal takedown.\n *\n * ## Why the gate lives HERE and not at the six call sites\n *\n * Six call-site edits close six holes and guarantee the seventh is forgotten.\n * Gating in the job contract means the seventh job is covered the day it is\n * written: `lockdown` below is REQUIRED, so a new `registerPluginJob({…})`\n * does not compile until its author has answered \"what does this touch\".\n *\n * (The nx boundary is NOT why. `libs/plugins/*` CAN import\n * `@aglyn/tenant-data-admin` — it carries `scope:data` and `scope:aglyn`,\n * both on the `aglyn:addons` allowlist, and 196 files in `libs/plugins`\n * already do. The AGL-2495 sweep recorded the opposite as its reason for\n * deferring; it was wrong about the constraint and right about the design.)\n *\n * ## Why core cannot just call `getSiteLockdown` itself\n *\n * `@aglyn/tenant-data-admin` imports `@aglyn/aglyn/server`, so a core import\n * of it is a project CYCLE. Hence the same registry shape the platform uses\n * for every other app↔core edge (`registerBillingWebhookHandler`,\n * `registerSitePageResolver`): the host app\n * registers the resolver from a place that may import the admin lib, and core\n * looks it up. Nothing is imported statically in either direction and a\n * self-host build with a different carrier registers its own.\n *\n * ## Fail-open is INHERITED, not re-decided\n *\n * The gate asks whatever the app registered — on the tenant that is\n * `getSiteLockdown`, which owns the fail-open/fail-closed decision (and the\n * takedown-class ratchet on top of it). This module deliberately holds no\n * parallel notion of what a lock is: it has one question and no opinion.\n *=========================================*/\n\n/**\n * The one question a beat asks before touching a host: is a lock active that\n * forbids this write? Resolved by the host app, never by core.\n */\nexport type PluginJobHostLockedResolver = (\n hostId: string,\n) => Promise<boolean> | boolean\n\nlet hostLockedResolver: PluginJobHostLockedResolver | null = null\n\n/**\n * Registers the platform's per-host lockdown verdict for background work,\n * from a place that may import the admin data lib — on this platform,\n * `apps/tenant/utils/plugin-job-lockdown.ts`, loaded for its side effect by\n * the runner route beside the core job registrations.\n *\n * Idempotent and last-registration-wins, unlike a registry that holds a\n * capability with a plugin OWNER, where a second claimant is a\n * misconfiguration. This is infrastructure the deployment supplies exactly\n * once, and a process that loads the module twice must not throw on the\n * second pass.\n */\nexport function registerPluginJobHostLockdown(\n resolver: PluginJobHostLockedResolver,\n): void {\n hostLockedResolver = resolver\n}\n\n/** Has the deployment wired a verdict? Reported by the runner route. */\nexport function hasPluginJobHostLockdown(): boolean {\n return hostLockedResolver !== null\n}\n\n/** Test seam: forget the registration. */\nexport function resetPluginJobHostLockdownForTests(): void {\n hostLockedResolver = null\n}\n\n/**\n * The gate handed to a `per-host` job. One method, so there is exactly one\n * thing to call and one thing for the coverage guard to look for:\n *\n * if (await gate.isLocked(hostId)) continue\n *\n * SKIPPED, NOT DROPPED is the caller's obligation and the reason this\n * answers a question rather than performing the skip: `continue` leaves the\n * row exactly as it was, so the work lands on the next beat after the lift.\n * A gate that swallowed the row — stamping it done, deleting it, advancing a\n * cursor past it — would turn a pause into a cancellation, which is what\n * `publish-schedule-job.ts` argued at length and what every job here copies.\n */\nexport interface PluginJobHostGate {\n isLocked(hostId: string): Promise<boolean>\n}\n\n/**\n * The gate as the runner builds it.\n *\n * The MANUAL doors — `commerce/process-abandoned`, `commerce/process-restock`,\n * `bookings/reminders` — do NOT use it (AGL-3356). They are served by the\n * console, which registers no resolver, so this answered \"not locked\" for\n * every site there and a forced pass mailed a suspended site's customers.\n * They take `siteLockdownJobGate()` from the admin lib instead: the same\n * function the tenant registers here, needing no registration at all.\n *\n * NO resolver registered answers \"not locked\", loudly and once. That is the\n * fail-open direction on purpose: a deployment that has not wired a carrier\n * (a self-host, a test process) must not have every background job welded\n * shut by infrastructure it never asked for. The loss is real and is why\n * `hasPluginJobHostLockdown` is reported by the runner route rather than\n * left to be inferred from nothing happening.\n *\n * ONE VERDICT PER HOST PER GATE (AGL-4121). The resolver's answer costs three\n * Firestore reads (org id, org, host) and outside a request `React.cache`\n * dedupes none of them, so a scan that asked once per row paid three reads per\n * row for one site's answer. The runner mints a gate per job per run, so a\n * verdict held here lives exactly as long as the run that asked, the way the\n * sending-identity and branding maps in those scans do, and the next beat asks\n * afresh. A lock laid mid-run therefore reaches a site already asked about on\n * the next beat rather than on that run's next row.\n *\n * The promise is what is held, so askers running side by side\n * (`pos-cellular-billing` asks under a `Promise.all`) share one read. A\n * resolver that failed gave no verdict, so nothing is kept and the next row\n * asks again, as it always has.\n */\nexport function pluginJobHostGate(): PluginJobHostGate {\n const verdicts = new Map<string, Promise<boolean>>()\n const ask = async (hostId: string): Promise<boolean> => {\n if (!hostId) return false\n if (!hostLockedResolver) {\n warnMissingResolver()\n return false\n }\n return (await hostLockedResolver(hostId)) === true\n }\n return {\n isLocked: (hostId: string) => {\n const held = verdicts.get(hostId)\n if (held) return held\n const verdict = ask(hostId)\n verdicts.set(hostId, verdict)\n verdict.catch(() => verdicts.delete(hostId))\n return verdict\n },\n }\n}\n\nlet warnedMissingResolver = false\nfunction warnMissingResolver(): void {\n if (warnedMissingResolver) return\n warnedMissingResolver = true\n console.error(\n '[plugin-jobs] no host lockdown resolver is registered — background ' +\n 'jobs are running UNGATED. Register one with ' +\n 'registerPluginJobHostLockdown() from the app that owns the runner.',\n )\n}\n\n/** Test seam: let the \"warned once\" latch be re-armed. */\nexport function resetPluginJobLockdownWarningForTests(): void {\n warnedMissingResolver = false\n}\n\n/**\n * What a job touches, and therefore what the runner owes it. REQUIRED on\n * every registration — the point of the field is that a new job cannot\n * compile without its author answering.\n */\nexport type PluginJobLockdown =\n /**\n * The job acts FOR HOSTS. The runner injects a {@link PluginJobHostGate}\n * and the handler must ask it for every host it is about to touch.\n */\n | { scope: 'per-host' }\n /**\n * The job touches nothing a lock could be about. `reason` is the argument,\n * and `lockdown-tenant-api-coverage.spec.ts` holds the set by name so a new\n * one has to be argued rather than declared.\n */\n | { scope: 'platform'; reason: string }\n\nexport interface PluginJob {\n pluginId: string\n /** Stable job name, unique within the plugin ('expire-stale-holds'). */\n name: string\n intervalMinutes: number\n description?: string\n /** See {@link PluginJobLockdown}. Required, deliberately. */\n lockdown: PluginJobLockdown\n handler: (gate: PluginJobHostGate) => Promise<void> | void\n}\n\nconst jobs = new Map<string, PluginJob>()\n\nexport const pluginJobKey = (job: Pick<PluginJob, 'pluginId' | 'name'>) =>\n `${job.pluginId}:${job.name}`\n\n/** Idempotent per pluginId:name. */\nexport function registerPluginJob(job: PluginJob): void {\n jobs.set(pluginJobKey(job), job)\n}\n\nexport function listPluginJobs(): PluginJob[] {\n return [...jobs.values()]\n}\n\nexport interface PluginJobResult {\n key: string\n ok: boolean\n error?: string\n}\n\n/**\n * The gate a `platform`-scoped job gets: one that REFUSES to answer.\n *\n * A declaration of `platform` scope is a claim that there is no host to ask\n * about. If such a job asks anyway, the claim is false and the registration\n * is the thing that needs fixing — so the runner surfaces it as that job's\n * own failure (isolated, like every other job error) instead of quietly\n * answering \"not locked\" and letting a mislabelled job mutate a locked site.\n *\n * This is the one place the contract is enforced at RUNTIME rather than by\n * the type or by the guard, and it is enforceable precisely because asking\n * is the only way to get an answer.\n */\nfunction refusingGate(key: string): PluginJobHostGate {\n return {\n isLocked: async (hostId: string) => {\n throw new Error(\n `plugin job ${key} declared lockdown scope 'platform' but asked ` +\n `about host \"${hostId}\". Declare { scope: 'per-host' } instead.`,\n )\n },\n }\n}\n\n/**\n * Runs the jobs `due` selects (default: all), sequentially with error\n * isolation — one broken job never blocks the rest.\n *\n * Every handler is handed a {@link PluginJobHostGate} (AGL-2495). It is a\n * parameter rather than something the handler imports so that the runner\n * stays the single place the verdict comes from: swap what\n * `registerPluginJobHostLockdown` holds and every job on the beat changes\n * behaviour together, including one written next month.\n */\nexport async function runPluginJobs(\n due?: (job: PluginJob) => boolean,\n): Promise<PluginJobResult[]> {\n const results: PluginJobResult[] = []\n for (const job of jobs.values()) {\n if (due && !due(job)) continue\n const key = pluginJobKey(job)\n try {\n await job.handler(\n job.lockdown?.scope === 'platform'\n ? refusingGate(key)\n : pluginJobHostGate(),\n )\n results.push({ key, ok: true })\n } catch (error) {\n console.error(`plugin job ${key} failed:`, error)\n results.push({ key, ok: false, error: String(error) })\n }\n }\n return results\n}\n"],"names":["hostLockedResolver","registerPluginJobHostLockdown","resolver","hasPluginJobHostLockdown","resetPluginJobHostLockdownForTests","pluginJobHostGate","verdicts","Map","ask","hostId","warnMissingResolver","isLocked","held","get","verdict","set","catch","delete","warnedMissingResolver","console","error","resetPluginJobLockdownWarningForTests","jobs","pluginJobKey","job","pluginId","name","registerPluginJob","listPluginJobs","values","refusingGate","key","Error","runPluginJobs","due","results","handler","lockdown","scope","push","ok","String"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;CAOC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2CAwC2C,GAE3C;;;CAGC,GAKD,IAAIA,qBAAyD;AAE7D;;;;;;;;;;;CAWC,GACD,OAAO,SAASC,8BACdC,QAAqC;IAErCF,qBAAqBE;AACvB;AAEA,sEAAsE,GACtE,OAAO,SAASC;IACd,OAAOH,uBAAuB;AAChC;AAEA,wCAAwC,GACxC,OAAO,SAASI;IACdJ,qBAAqB;AACvB;AAmBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8BC,GACD,OAAO,SAASK;IACd,MAAMC,WAAW,IAAIC;IACrB,MAAMC,MAAM,OAAOC;QACjB,IAAI,CAACA,QAAQ,OAAO;QACpB,IAAI,CAACT,oBAAoB;YACvBU;YACA,OAAO;QACT;QACA,OAAO,AAAC,MAAMV,mBAAmBS,YAAa;IAChD;IACA,OAAO;QACLE,UAAU,CAACF;YACT,MAAMG,OAAON,SAASO,GAAG,CAACJ;YAC1B,IAAIG,MAAM,OAAOA;YACjB,MAAME,UAAUN,IAAIC;YACpBH,SAASS,GAAG,CAACN,QAAQK;YACrBA,QAAQE,KAAK,CAAC,IAAMV,SAASW,MAAM,CAACR;YACpC,OAAOK;QACT;IACF;AACF;AAEA,IAAII,wBAAwB;AAC5B,SAASR;IACP,IAAIQ,uBAAuB;IAC3BA,wBAAwB;IACxBC,QAAQC,KAAK,CACX,wEACE,iDACA;AAEN;AAEA,wDAAwD,GACxD,OAAO,SAASC;IACdH,wBAAwB;AAC1B;AA+BA,MAAMI,OAAO,IAAIf;AAEjB,OAAO,MAAMgB,eAAe,CAACC,MAC3B,GAAGA,IAAIC,QAAQ,CAAC,CAAC,EAAED,IAAIE,IAAI,EAAE,CAAA;AAE/B,kCAAkC,GAClC,OAAO,SAASC,kBAAkBH,GAAc;IAC9CF,KAAKP,GAAG,CAACQ,aAAaC,MAAMA;AAC9B;AAEA,OAAO,SAASI;IACd,OAAO;WAAIN,KAAKO,MAAM;KAAG;AAC3B;AAQA;;;;;;;;;;;;CAYC,GACD,SAASC,aAAaC,GAAW;IAC/B,OAAO;QACLpB,UAAU,OAAOF;YACf,MAAM,IAAIuB,MACR,CAAC,WAAW,EAAED,IAAI,8CAA8C,CAAC,GAC/D,CAAC,YAAY,EAAEtB,OAAO,yCAAyC,CAAC;QAEtE;IACF;AACF;AAEA;;;;;;;;;CASC,GACD,OAAO,eAAewB,cACpBC,GAAiC;IAEjC,MAAMC,UAA6B,EAAE;IACrC,KAAK,MAAMX,OAAOF,KAAKO,MAAM,GAAI;QAC/B,IAAIK,OAAO,CAACA,IAAIV,MAAM;QACtB,MAAMO,MAAMR,aAAaC;QACzB,IAAI;gBAEAA;YADF,MAAMA,IAAIY,OAAO,CACfZ,EAAAA,gBAAAA,IAAIa,QAAQ,qBAAZb,cAAcc,KAAK,MAAK,aACpBR,aAAaC,OACb1B;YAEN8B,QAAQI,IAAI,CAAC;gBAAER;gBAAKS,IAAI;YAAK;QAC/B,EAAE,OAAOpB,OAAO;YACdD,QAAQC,KAAK,CAAC,CAAC,WAAW,EAAEW,IAAI,QAAQ,CAAC,EAAEX;YAC3Ce,QAAQI,IAAI,CAAC;gBAAER;gBAAKS,IAAI;gBAAOpB,OAAOqB,OAAOrB;YAAO;QACtD;IACF;IACA,OAAOe;AACT"}
|
|
@@ -102,8 +102,11 @@ export interface PluginRecordEntryContext {
|
|
|
102
102
|
/** The plugin filing it, recorded on the entry. */
|
|
103
103
|
sourcePluginId: string;
|
|
104
104
|
}
|
|
105
|
-
/**
|
|
106
|
-
|
|
105
|
+
/**
|
|
106
|
+
* What an activity is, in the record system's own words — a message kind
|
|
107
|
+
* (`text`, `whatsapp`, …) for a plugin that carries one on that channel.
|
|
108
|
+
*/
|
|
109
|
+
export type PluginRecordActivityKind = 'email' | 'call' | 'text' | 'whatsapp' | 'messenger' | 'instagram' | 'linkedin' | 'meeting' | 'note' | 'other';
|
|
107
110
|
/** The message an `email` activity is about. */
|
|
108
111
|
export interface PluginRecordEmail {
|
|
109
112
|
/** `outbound` for one the workspace wrote; `inbound` for one written to it. */
|