@ploaness/governance 1.0.0 → 1.1.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.
Files changed (92) hide show
  1. package/dist/a11y-hit-target.d.ts +35 -0
  2. package/dist/a11y-hit-target.d.ts.map +1 -0
  3. package/dist/a11y-hit-target.js +102 -0
  4. package/dist/a11y-hit-target.js.map +1 -0
  5. package/dist/a11y-incomplete.d.ts +18 -0
  6. package/dist/a11y-incomplete.d.ts.map +1 -0
  7. package/dist/a11y-incomplete.js +82 -0
  8. package/dist/a11y-incomplete.js.map +1 -0
  9. package/dist/access-boundary.d.ts +19 -5
  10. package/dist/access-boundary.d.ts.map +1 -1
  11. package/dist/access-boundary.js +58 -4
  12. package/dist/access-boundary.js.map +1 -1
  13. package/dist/admin-view-coverage.d.ts +25 -0
  14. package/dist/admin-view-coverage.d.ts.map +1 -0
  15. package/dist/admin-view-coverage.js +90 -0
  16. package/dist/admin-view-coverage.js.map +1 -0
  17. package/dist/axe-coverage.d.ts +41 -0
  18. package/dist/axe-coverage.d.ts.map +1 -0
  19. package/dist/axe-coverage.js +95 -0
  20. package/dist/axe-coverage.js.map +1 -0
  21. package/dist/banned-typography.d.ts.map +1 -1
  22. package/dist/banned-typography.js +26 -0
  23. package/dist/banned-typography.js.map +1 -1
  24. package/dist/build-diagnosis.d.ts +10 -0
  25. package/dist/build-diagnosis.d.ts.map +1 -0
  26. package/dist/build-diagnosis.js +38 -0
  27. package/dist/build-diagnosis.js.map +1 -0
  28. package/dist/document-references.d.ts.map +1 -1
  29. package/dist/document-references.js +7 -1
  30. package/dist/document-references.js.map +1 -1
  31. package/dist/environment-coherence.d.ts +87 -0
  32. package/dist/environment-coherence.d.ts.map +1 -0
  33. package/dist/environment-coherence.js +150 -0
  34. package/dist/environment-coherence.js.map +1 -0
  35. package/dist/generated-drift.d.ts +18 -0
  36. package/dist/generated-drift.d.ts.map +1 -0
  37. package/dist/generated-drift.js +35 -0
  38. package/dist/generated-drift.js.map +1 -0
  39. package/dist/index.d.ts +10 -1
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +10 -1
  42. package/dist/index.js.map +1 -1
  43. package/dist/payload-access.d.ts.map +1 -1
  44. package/dist/payload-access.js +23 -49
  45. package/dist/payload-access.js.map +1 -1
  46. package/dist/payload-configs.d.ts +24 -0
  47. package/dist/payload-configs.d.ts.map +1 -0
  48. package/dist/payload-configs.js +77 -0
  49. package/dist/payload-configs.js.map +1 -0
  50. package/dist/payload-field-access.d.ts +4 -0
  51. package/dist/payload-field-access.d.ts.map +1 -0
  52. package/dist/payload-field-access.js +51 -0
  53. package/dist/payload-field-access.js.map +1 -0
  54. package/dist/payload-policy.d.ts.map +1 -1
  55. package/dist/payload-policy.js +111 -13
  56. package/dist/payload-policy.js.map +1 -1
  57. package/dist/payload-source.js +2 -2
  58. package/dist/payload-source.js.map +1 -1
  59. package/dist/relationship-cleanup.d.ts +18 -0
  60. package/dist/relationship-cleanup.d.ts.map +1 -0
  61. package/dist/relationship-cleanup.js +96 -0
  62. package/dist/relationship-cleanup.js.map +1 -0
  63. package/dist/route-coverage.d.ts +27 -0
  64. package/dist/route-coverage.d.ts.map +1 -0
  65. package/dist/route-coverage.js +57 -0
  66. package/dist/route-coverage.js.map +1 -0
  67. package/dist/route-map.d.ts +65 -0
  68. package/dist/route-map.d.ts.map +1 -0
  69. package/dist/route-map.js +183 -0
  70. package/dist/route-map.js.map +1 -0
  71. package/dist/secret-policy.js +4 -4
  72. package/dist/secret-policy.js.map +1 -1
  73. package/dist/settings.d.ts +10 -0
  74. package/dist/settings.d.ts.map +1 -1
  75. package/dist/settings.js +5 -1
  76. package/dist/settings.js.map +1 -1
  77. package/dist/source-security.d.ts +4 -0
  78. package/dist/source-security.d.ts.map +1 -0
  79. package/dist/source-security.js +56 -0
  80. package/dist/source-security.js.map +1 -0
  81. package/dist/source-text.d.ts +14 -0
  82. package/dist/source-text.d.ts.map +1 -1
  83. package/dist/source-text.js +39 -0
  84. package/dist/source-text.js.map +1 -1
  85. package/dist/version-policy.d.ts.map +1 -1
  86. package/dist/version-policy.js +31 -0
  87. package/dist/version-policy.js.map +1 -1
  88. package/package.json +1 -1
  89. package/dist/url-safety.d.ts +0 -12
  90. package/dist/url-safety.d.ts.map +0 -1
  91. package/dist/url-safety.js +0 -58
  92. package/dist/url-safety.js.map +0 -1
@@ -0,0 +1,35 @@
1
+ /** What a browser must report about a control before this module can classify it. */
2
+ export interface HitTargetProbe {
3
+ /** Border-box width in CSS pixels. */
4
+ readonly width: number;
5
+ /** Border-box height in CSS pixels. */
6
+ readonly height: number;
7
+ /** The computed `clip-path`, verbatim, or `none`. */
8
+ readonly clipPath: string;
9
+ /** The computed `clip`, verbatim, or `auto`. */
10
+ readonly clip: string;
11
+ /**
12
+ * Whether `elementFromPoint` at the box centre returned this element or a descendant of it.
13
+ *
14
+ * This is the same question Playwright's actionability check asks, and asking it here is what lets
15
+ * a clipped element and an obscured one be told apart rather than both reported as a timeout.
16
+ */
17
+ readonly ownsCentrePoint: boolean;
18
+ }
19
+ /** How a control can be reached, and therefore which passes of the sweep apply to it. */
20
+ export type HitTargetVerdict = 'hoverable' | 'visually-hidden' | 'obscured';
21
+ /** The verdict together with the sentence a report prints for it. */
22
+ export interface HitTargetClassification {
23
+ readonly verdict: HitTargetVerdict;
24
+ readonly reason: string;
25
+ }
26
+ /**
27
+ * Decide how a control can be reached, so the sweep hovers what it can and explains what it cannot.
28
+ *
29
+ * A control that owns its own centre point is hoverable whatever its styling, which is what keeps a
30
+ * decorative `clip-path` on a real button from being mistaken for a hiding technique.
31
+ * @param probe what the browser reports about the control.
32
+ * @returns the verdict and the sentence describing it.
33
+ */
34
+ export declare const classifyHitTarget: (probe: HitTargetProbe) => HitTargetClassification;
35
+ //# sourceMappingURL=a11y-hit-target.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"a11y-hit-target.d.ts","sourceRoot":"","sources":["../src/a11y-hit-target.ts"],"names":[],"mappings":"AAsBA,qFAAqF;AACrF,MAAM,WAAW,cAAc;IAC7B,sCAAsC;IACtC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,uCAAuC;IACvC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,qDAAqD;IACrD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,gDAAgD;IAChD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAA;CAClC;AAED,yFAAyF;AACzF,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG,iBAAiB,GAAG,UAAU,CAAA;AAE3E,qEAAqE;AACrE,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAA;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAyED;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,GAAI,OAAO,cAAc,KAAG,uBAkBzD,CAAA"}
@@ -0,0 +1,102 @@
1
+ // Whether a control can be hovered, and what it means when it cannot.
2
+ //
3
+ // The shipped accessibility sweep hovers every control in the site chrome to measure hover contrast.
4
+ // A skip link hidden with `clip-path: inset(50%)` - the standard accessible pattern - has no hit
5
+ // target, because a clip removes an element from hit testing as well as from painting. Playwright
6
+ // therefore retried the hover until the whole test timed out, and reported `<header> intercepts
7
+ // pointer events`: a message that blames the ancestor the browser found instead, and sends the reader
8
+ // to weaken a skip link that was correct.
9
+ //
10
+ // The repair cannot be "catch the failure and move on". A VISIBLE control that intercepts pointer
11
+ // events is a real page defect, and swallowing both cases would trade a misleading failure for no
12
+ // failure at all. So this decides between them, and the two answers differ:
13
+ //
14
+ // - visually hidden: skip the hover pass and say why. Nothing is lost, because hover contrast is a
15
+ // measurement of painted pixels and this element paints none. The focus pass still runs, which is
16
+ // the state that actually matters for a skip link.
17
+ // - obscured: something is on top of a control that does paint. That is the defect the sweep is for,
18
+ // and it should be reported as such rather than as a timeout.
19
+ //
20
+ // The classification is deliberately conservative: anything it cannot place is hoverable, so an
21
+ // undecided case runs the check rather than skipping it.
22
+ // The legacy visually-hidden pattern draws a 1px box and clips it away, so its box has area and its
23
+ // paint does not. Anything at or under this in either dimension is that pattern rather than a control.
24
+ const HIDDEN_BOX_LIMIT = 1;
25
+ // An `inset()` is empty when its opposing insets meet or cross, which is arithmetic rather than a
26
+ // spelling: `inset(50%)` collapses both axes, and so do `inset(50% 0)` and `inset(60% 10% 60% 10%)`.
27
+ // Matching the literal `inset(50%)` would have covered the one form every visually-hidden helper class
28
+ // ships and quietly missed the rest, so the shorthand is expanded and the pairs are added instead.
29
+ const FULL_EXTENT = 100;
30
+ const INSET_SHORTHAND = /^inset\(([^)]*)\)$/;
31
+ // `inset()` may carry a `round <radius>` clause, which describes corners rather than extent.
32
+ const ROUND_CLAUSE = /\sround\s[\s\S]*$/;
33
+ const PERCENT = '%';
34
+ // A percentage is the only unit that can be reasoned about without knowing the element's size, and it
35
+ // is the unit the pattern is always written in. A pixel inset is left undecided, which classifies the
36
+ // control as reachable and runs the check.
37
+ const asPercentage = (token) => token.endsWith(PERCENT) ? Number(token.slice(0, -PERCENT.length)) : Number.NaN;
38
+ // CSS box shorthand: one value applies to all four sides, two to the vertical and horizontal pairs,
39
+ // three add a distinct bottom, four are top/right/bottom/left in order.
40
+ const expandBoxShorthand = (values) => {
41
+ const [first, second, third, fourth] = values;
42
+ if (first === undefined) {
43
+ return [];
44
+ }
45
+ const right = second ?? first;
46
+ return [first, right, third ?? first, fourth ?? right];
47
+ };
48
+ const isEmptyInset = (argument) => {
49
+ const values = expandBoxShorthand(argument
50
+ .replace(ROUND_CLAUSE, '')
51
+ .split(/[\s,]+/)
52
+ .filter((token) => token.length > 0)
53
+ .map((token) => asPercentage(token)));
54
+ const [top, right, bottom, left] = values;
55
+ if (top === undefined || right === undefined || bottom === undefined || left === undefined) {
56
+ return false;
57
+ }
58
+ return top + bottom >= FULL_EXTENT || left + right >= FULL_EXTENT;
59
+ };
60
+ // A circle or ellipse of no radius clips everything away. Only an explicit zero counts: a keyword
61
+ // radius such as `closest-side` depends on the box and is left undecided.
62
+ const ZERO_RADIUS_SHAPE = /^(?:circle|ellipse)\(\s*0(?:px|%|em|rem)?[\s,)]/;
63
+ // `clip: rect(...)` with no extent, in the spellings browsers compute it to. The 1px form is the
64
+ // classic `.sr-only` rule, which pairs a 1px box with a rect that keeps none of it.
65
+ const EMPTY_CLIP = /^rect\(\s*[01](?:px)?[\s,]+[01](?:px)?[\s,]+[01](?:px)?[\s,]+[01](?:px)?\s*\)$/;
66
+ const isEmptyClipPath = (clipPath) => {
67
+ const normalised = clipPath.trim().toLowerCase();
68
+ const inset = INSET_SHORTHAND.exec(normalised);
69
+ return inset === null ? ZERO_RADIUS_SHAPE.test(normalised) : isEmptyInset(inset[1] ?? '');
70
+ };
71
+ const isEmptyClip = (clip) => EMPTY_CLIP.test(clip.trim().toLowerCase());
72
+ const hasNoPaintedArea = (probe) => probe.width <= 0 ||
73
+ probe.height <= 0 ||
74
+ (probe.width <= HIDDEN_BOX_LIMIT && probe.height <= HIDDEN_BOX_LIMIT) ||
75
+ isEmptyClipPath(probe.clipPath) ||
76
+ isEmptyClip(probe.clip);
77
+ /**
78
+ * Decide how a control can be reached, so the sweep hovers what it can and explains what it cannot.
79
+ *
80
+ * A control that owns its own centre point is hoverable whatever its styling, which is what keeps a
81
+ * decorative `clip-path` on a real button from being mistaken for a hiding technique.
82
+ * @param probe what the browser reports about the control.
83
+ * @returns the verdict and the sentence describing it.
84
+ */
85
+ export const classifyHitTarget = (probe) => {
86
+ if (probe.ownsCentrePoint) {
87
+ return { verdict: 'hoverable', reason: 'the control is reachable at its own centre point' };
88
+ }
89
+ if (hasNoPaintedArea(probe)) {
90
+ return {
91
+ verdict: 'visually-hidden',
92
+ reason: 'the control is visually hidden (a zero-area box or a clip that removes it), so it has no ' +
93
+ 'hit target and no painted pixels whose contrast could be measured under hover',
94
+ };
95
+ }
96
+ return {
97
+ verdict: 'obscured',
98
+ reason: 'the control paints but something else answers at its centre point, so a pointer cannot ' +
99
+ 'reach it; this is a defect in the page rather than in the sweep',
100
+ };
101
+ };
102
+ //# sourceMappingURL=a11y-hit-target.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"a11y-hit-target.js","sourceRoot":"","sources":["../src/a11y-hit-target.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The axe `incomplete` message keys that describe a defect rather than a question.
3
+ *
4
+ * Exported so a project reading a finding can see the whole of what is enforced, and so a spec can
5
+ * assert the enforced set rather than a single member of it.
6
+ */
7
+ export declare const DEFINITE_INCOMPLETE_KEYS: readonly string[];
8
+ /**
9
+ * Pick the entries of an axe `incomplete` bucket that are defects rather than open questions.
10
+ *
11
+ * Takes `unknown` rather than axe's own result type because this package declares no dependencies and
12
+ * must not acquire one to read a shape it only ever inspects. The caller passes `scan.incomplete`
13
+ * straight through.
14
+ * @param incomplete the `incomplete` array of an axe result.
15
+ * @returns one human-readable finding per definite defect, in the order axe reported them.
16
+ */
17
+ export declare const findDefiniteIncomplete: (incomplete: unknown) => readonly string[];
18
+ //# sourceMappingURL=a11y-incomplete.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"a11y-incomplete.d.ts","sourceRoot":"","sources":["../src/a11y-incomplete.ts"],"names":[],"mappings":"AAoBA;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,EAAE,SAAS,MAAM,EAAmB,CAAA;AAuDzE;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,GAAI,YAAY,OAAO,KAAG,SAAS,MAAM,EAGpE,CAAA"}
@@ -0,0 +1,82 @@
1
+ // What to do with the axe bucket the shipped sweep used to discard.
2
+ //
3
+ // axe returns three buckets, not two: `violations`, `passes`, and `incomplete` - the last meaning "a
4
+ // check ran and could not decide". The accessibility sweep asserted `violations` alone, which reads as
5
+ // the obvious thing to write and silently excluded the single worst defect the sweep exists to catch.
6
+ //
7
+ // An exactly equal foreground and background is filed as incomplete rather than as a violation,
8
+ // because axe reads identical colours as text hidden on purpose and refers the question to a human.
9
+ // So a control drawn in its own background colour - contrast ratio 1:1, invisible to every reader -
10
+ // passed a gate built to measure contrast. A project confirmed it by mutation: removing the colour fix
11
+ // from the page left the assertion green.
12
+ //
13
+ // The answer is narrow on purpose. A blanket "incomplete must be empty" would be wrong for a rule no
14
+ // project can edit: text over a photograph, a gradient, or a partly transparent layer lands in this
15
+ // bucket legitimately, and axe defers there because the question genuinely needs a human. `equalRatio`
16
+ // is the one key that needs none - two identical colours cannot be a judgement call - so it is the one
17
+ // key that fails. The list is a floor to widen when another key turns out to be as unambiguous, not a
18
+ // ceiling.
19
+ import { asRecord, asText, isArray } from './json-shapes.js';
20
+ /**
21
+ * The axe `incomplete` message keys that describe a defect rather than a question.
22
+ *
23
+ * Exported so a project reading a finding can see the whole of what is enforced, and so a spec can
24
+ * assert the enforced set rather than a single member of it.
25
+ */
26
+ export const DEFINITE_INCOMPLETE_KEYS = ['equalRatio'];
27
+ // A node's checks live under three keys, and which one carries the result depends on how the rule was
28
+ // composed rather than on anything the reader chose. Reading only `any` would miss a rule expressed as
29
+ // `all`, which is a silence of exactly the kind this module exists to remove.
30
+ const CHECK_KEYS = ['any', 'all', 'none'];
31
+ // A node names itself by CSS selector, and axe nests the selector when the node is inside an iframe.
32
+ // Joining rather than taking the first keeps a finding pointing at the element a reader has to open.
33
+ const TARGET_SEPARATOR = ' >>> ';
34
+ const targetOf = (node) => {
35
+ const target = asRecord(node)['target'];
36
+ if (!isArray(target)) {
37
+ return '(unknown element)';
38
+ }
39
+ const parts = target.map((part) => asText(part));
40
+ return parts.length === 0 ? '(unknown element)' : parts.join(TARGET_SEPARATOR);
41
+ };
42
+ const checksOf = (node) => CHECK_KEYS.flatMap((key) => {
43
+ const checks = asRecord(node)[key];
44
+ return isArray(checks) ? checks : [];
45
+ });
46
+ // The message axe itself wrote, rather than one restated here. A locale-aware runner returns the
47
+ // translated sentence, and a finding that quoted an English copy would disagree with the report the
48
+ // same run printed.
49
+ const describeCheck = (ruleId, node, check) => {
50
+ const message = asText(asRecord(check)['message']);
51
+ const stated = message.length > 0 ? message : 'axe reported no message';
52
+ return `${ruleId} on ${targetOf(node)}: ${stated}`;
53
+ };
54
+ // axe names the reason it could not decide under `data.messageKey`, which is the only field here that
55
+ // is a stable identifier rather than prose: the message beside it is localised.
56
+ const messageKeyOf = (check) => {
57
+ const data = asRecord(asRecord(check)['data']);
58
+ return asText(data['messageKey']);
59
+ };
60
+ const definiteChecks = (ruleId, node) => checksOf(node)
61
+ .filter((check) => DEFINITE_INCOMPLETE_KEYS.includes(messageKeyOf(check)))
62
+ .map((check) => describeCheck(ruleId, node, check));
63
+ const definiteNodes = (result) => {
64
+ const nodes = asRecord(result)['nodes'];
65
+ const ruleId = asText(asRecord(result)['id']);
66
+ return isArray(nodes)
67
+ ? nodes.flatMap((node) => definiteChecks(ruleId, node))
68
+ : [];
69
+ };
70
+ /**
71
+ * Pick the entries of an axe `incomplete` bucket that are defects rather than open questions.
72
+ *
73
+ * Takes `unknown` rather than axe's own result type because this package declares no dependencies and
74
+ * must not acquire one to read a shape it only ever inspects. The caller passes `scan.incomplete`
75
+ * straight through.
76
+ * @param incomplete the `incomplete` array of an axe result.
77
+ * @returns one human-readable finding per definite defect, in the order axe reported them.
78
+ */
79
+ export const findDefiniteIncomplete = (incomplete) => isArray(incomplete)
80
+ ? incomplete.flatMap((result) => definiteNodes(result))
81
+ : [];
82
+ //# sourceMappingURL=a11y-incomplete.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"a11y-incomplete.js","sourceRoot":"","sources":["../src/a11y-incomplete.ts"],"names":[],"mappings":"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"}
@@ -7,18 +7,32 @@ export type ReportedPermission = boolean | {
7
7
  readonly permission?: boolean;
8
8
  readonly where?: unknown;
9
9
  };
10
- /** One entity's operations, keyed as Payload keys them. */
11
- export type ReportedEntity = Readonly<Record<string, ReportedPermission>>;
12
- /** The body of `/api/access`, read for the operations alone. */
10
+ /**
11
+ * A `fields` map as Payload sends it, or the bare `true` its sanitisation collapses a fully permitted
12
+ * one to - which says every field without naming one, and so cannot be enumerated.
13
+ */
14
+ export type ReportedFields = boolean | Readonly<Record<string, ReportedEntity>>;
15
+ /**
16
+ * One entity's operations and, beside them, the per-field map Payload reports for it.
17
+ *
18
+ * A field is reported in exactly the shape an entity is - its own operations, and its own `fields` when
19
+ * it is an array or a group - so one type describes both and the walk over them is the same walk.
20
+ */
21
+ export interface ReportedEntity {
22
+ readonly fields?: ReportedFields;
23
+ readonly [operation: string]: ReportedFields | ReportedPermission | undefined;
24
+ }
25
+ /** The body of `/api/access`, read for the operations and the fields beneath them. */
13
26
  export interface AccessReport {
14
27
  readonly canAccessAdmin?: boolean;
15
28
  readonly collections?: Readonly<Record<string, ReportedEntity>>;
16
29
  readonly globals?: Readonly<Record<string, ReportedEntity>>;
17
30
  }
18
- /** One permission the running application actually grants. */
31
+ /** One permission the running application actually grants, on an entity or on one field of it. */
19
32
  export interface Granted {
20
33
  readonly entity: string;
21
34
  readonly operation: string;
35
+ readonly field?: string;
22
36
  }
23
37
  /**
24
38
  * A write is never a default a project should be able to reach by accident: Payload grants none of these
@@ -27,7 +41,7 @@ export interface Granted {
27
41
  */
28
42
  export declare const JUDGED_OPERATIONS: readonly string[];
29
43
  /** Whether Payload reported this operation as permitted, in either of the two shapes it sends. */
30
- export declare const isPermitted: (permission: ReportedPermission | undefined) => boolean;
44
+ export declare const isPermitted: (permission: ReportedFields | ReportedPermission | undefined) => boolean;
31
45
  /** Every permission an access report grants, across collections and globals alike. */
32
46
  export declare const grantedPermissions: (report: AccessReport) => readonly Granted[];
33
47
  /** How one grant is named, both in a report and in the `publicAccess` entry that would declare it. */
@@ -1 +1 @@
1
- {"version":3,"file":"access-boundary.d.ts","sourceRoot":"","sources":["../src/access-boundary.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAEjD;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAC1B,OAAO,GACP;IAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAA;AAE/D,2DAA2D;AAC3D,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC,CAAA;AAEzE,gEAAgE;AAChE,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAA;IACjC,QAAQ,CAAC,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAA;IAC/D,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAA;CAC5D;AAED,8DAA8D;AAC9D,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,EAAE,SAAS,MAAM,EAA2C,CAAA;AAE1F,kGAAkG;AAClG,eAAO,MAAM,WAAW,GAAI,YAAY,kBAAkB,GAAG,SAAS,KAAG,OACO,CAAA;AAUhF,sFAAsF;AACtF,eAAO,MAAM,kBAAkB,GAAI,QAAQ,YAAY,KAAG,SAAS,OAAO,EAGzE,CAAA;AAED,sGAAsG;AACtG,eAAO,MAAM,aAAa,GAAI,SAAS,OAAO,KAAG,MAAkD,CAAA;AAQnG;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAC3B,QAAQ,YAAY,EACpB,UAAU,SAAS,YAAY,EAAE,KAChC,SAAS,MAAM,EAG4C,CAAA"}
1
+ {"version":3,"file":"access-boundary.d.ts","sourceRoot":"","sources":["../src/access-boundary.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAEjD;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAC1B,OAAO,GACP;IAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAA;AAE/D;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAA;AAE/E;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAA;IAChC,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,cAAc,GAAG,kBAAkB,GAAG,SAAS,CAAA;CAC9E;AAED,sFAAsF;AACtF,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAA;IACjC,QAAQ,CAAC,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAA;IAC/D,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAA;CAC5D;AAED,kGAAkG;AAClG,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CACxB;AAED;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,EAAE,SAAS,MAAM,EAA2C,CAAA;AAO1F,kGAAkG;AAClG,eAAO,MAAM,WAAW,GACtB,YAAY,cAAc,GAAG,kBAAkB,GAAG,SAAS,KAC1D,OASF,CAAA;AA0DD,sFAAsF;AACtF,eAAO,MAAM,kBAAkB,GAAI,QAAQ,YAAY,KAAG,SAAS,OAAO,EAGzE,CAAA;AAED,sGAAsG;AACtG,eAAO,MAAM,aAAa,GAAI,SAAS,OAAO,KAAG,MAG8B,CAAA;AAiB/E;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAC3B,QAAQ,YAAY,EACpB,UAAU,SAAS,YAAY,EAAE,KAChC,SAAS,MAAM,EAG4C,CAAA"}
@@ -4,17 +4,71 @@
4
4
  * a public site legitimately grants, which is what `publicAccess` exists to record.
5
5
  */
6
6
  export const JUDGED_OPERATIONS = ['create', 'read', 'update', 'delete'];
7
+ /** The path standing in for a `fields` map Payload collapsed, which grants every field at once. */
8
+ const EVERY_FIELD = '*';
9
+ const PATH_SEPARATOR = '.';
7
10
  /** Whether Payload reported this operation as permitted, in either of the two shapes it sends. */
8
- export const isPermitted = (permission) => typeof permission === 'boolean' ? permission : permission?.permission === true;
9
- const grantsIn = (entities) => Object.entries(entities).flatMap(([entity, permissions]) => JUDGED_OPERATIONS.filter((operation) => isPermitted(permissions[operation])).map((operation) => ({ entity, operation })));
11
+ export const isPermitted = (permission) => {
12
+ if (permission === undefined || typeof permission === 'boolean') {
13
+ return permission === true;
14
+ }
15
+ // Both remaining shapes carry an index signature or an optional `permission`, so both are readable
16
+ // through one accessor rather than through a guard that would have to tell them apart by a key a
17
+ // field could legitimately be named.
18
+ const constrained = permission;
19
+ return constrained.permission === true;
20
+ };
21
+ /**
22
+ * Every field path one operation reaches, joined as Payload nests them.
23
+ *
24
+ * Recursive because an array or group field carries a `fields` map of its own, so a leaf is named by
25
+ * the path that reaches it (`playerFleet.shipKind`) rather than by its own name alone, which would
26
+ * collide between two arrays sharing a field name.
27
+ */
28
+ const fieldPathsFor = (fields, operation, prefix) => {
29
+ if (fields === undefined) {
30
+ return [];
31
+ }
32
+ if (typeof fields === 'boolean') {
33
+ return fields ? [`${prefix}${EVERY_FIELD}`] : [];
34
+ }
35
+ return Object.entries(fields).flatMap(([name, reported]) => [
36
+ ...(isPermitted(reported[operation]) ? [`${prefix}${name}`] : []),
37
+ ...fieldPathsFor(reported.fields, operation, `${prefix}${name}${PATH_SEPARATOR}`),
38
+ ]);
39
+ };
40
+ const grantsForOperation = (entity, permissions, operation) => [
41
+ { entity, operation },
42
+ ...fieldPathsFor(permissions.fields, operation, '').map((field) => ({ entity, operation, field })),
43
+ ];
44
+ // The field map is walked only for an operation the ENTITY grants, and that is a correctness rule
45
+ // rather than a saving. Payload reports a field's verdict whether or not the operation carrying it is
46
+ // reachable: an auth collection that denies `read` to a stranger still lists fields carrying
47
+ // `read: true`, because the field rule genuinely says yes and the collection rule is what stops the
48
+ // caller. Judging those would name permissions nobody holds, and a project cannot close a finding that
49
+ // describes no exposure - so it would close it by declaring it, which teaches exactly the wrong habit.
50
+ const grantsForEntity = (entity, permissions) => JUDGED_OPERATIONS.filter((operation) => isPermitted(permissions[operation])).flatMap((operation) => grantsForOperation(entity, permissions, operation));
51
+ const grantsIn = (entities) => Object.entries(entities).flatMap(([entity, permissions]) => grantsForEntity(entity, permissions));
10
52
  /** Every permission an access report grants, across collections and globals alike. */
11
53
  export const grantedPermissions = (report) => [
12
54
  ...grantsIn(report.collections ?? {}),
13
55
  ...grantsIn(report.globals ?? {}),
14
56
  ];
15
57
  /** How one grant is named, both in a report and in the `publicAccess` entry that would declare it. */
16
- export const describeGrant = (granted) => `${granted.entity}.${granted.operation}`;
17
- const isDeclared = (granted, declared) => declared.some((entry) => entry.entity === granted.entity && entry.operation === granted.operation);
58
+ export const describeGrant = (granted) => granted.field === undefined
59
+ ? `${granted.entity}.${granted.operation}`
60
+ : `${granted.entity}.${granted.operation}${PATH_SEPARATOR}${granted.field}`;
61
+ // A field is matched by name and never by a wildcard the project could write for itself. An entry
62
+ // listing `*` covers only the grant Payload itself collapsed to `*`, where every field is open and the
63
+ // declaration is therefore exact rather than blanket. Nothing here lets one declaration stand for a
64
+ // field added later, which is the whole reason the field half is judged at all.
65
+ const coversField = (entry, field) => (entry.fields ?? []).includes(field);
66
+ const isDeclared = (granted, declared) => declared.some((entry) => {
67
+ if (entry.entity !== granted.entity || entry.operation !== granted.operation) {
68
+ return false;
69
+ }
70
+ return granted.field === undefined ? true : coversField(entry, granted.field);
71
+ });
18
72
  /**
19
73
  * The grants the running application makes to a stranger that the project has not recorded. Empty is
20
74
  * the only passing answer: a project that declares nothing is judged most strictly, and a declaration
@@ -1 +1 @@
1
- {"version":3,"file":"access-boundary.js","sourceRoot":"","sources":["../src/access-boundary.ts"],"names":[],"mappings":"AAyCA;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAA;AAE1F,kGAAkG;AAClG,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,UAA0C,EAAW,EAAE,CACjF,OAAO,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI,CAAA;AAEhF,MAAM,QAAQ,GAAG,CAAC,QAAkD,EAAsB,EAAE,CAC1F,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,OAAO,CAC9B,CAAC,CAAC,MAAM,EAAE,WAAW,CAAoC,EAAsB,EAAE,CAC/E,iBAAiB,CAAC,MAAM,CAAC,CAAC,SAAiB,EAAW,EAAE,CACtD,WAAW,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CACpC,CAAC,GAAG,CAAC,CAAC,SAAiB,EAAW,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC,CACjE,CAAA;AAEH,sFAAsF;AACtF,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,MAAoB,EAAsB,EAAE,CAAC;IAC9E,GAAG,QAAQ,CAAC,MAAM,CAAC,WAAW,IAAI,EAAE,CAAC;IACrC,GAAG,QAAQ,CAAC,MAAM,CAAC,OAAO,IAAI,EAAE,CAAC;CAClC,CAAA;AAED,sGAAsG;AACtG,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,OAAgB,EAAU,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,SAAS,EAAE,CAAA;AAEnG,MAAM,UAAU,GAAG,CAAC,OAAgB,EAAE,QAAiC,EAAW,EAAE,CAClF,QAAQ,CAAC,IAAI,CACX,CAAC,KAAmB,EAAW,EAAE,CAC/B,KAAK,CAAC,MAAM,KAAK,OAAO,CAAC,MAAM,IAAI,KAAK,CAAC,SAAS,KAAK,OAAO,CAAC,SAAS,CAC3E,CAAA;AAEH;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAC9B,MAAoB,EACpB,QAAiC,EACd,EAAE,CACrB,kBAAkB,CAAC,MAAM,CAAC;KACvB,MAAM,CAAC,CAAC,OAAgB,EAAW,EAAE,CAAC,CAAC,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;KACrE,GAAG,CAAC,CAAC,OAAgB,EAAU,EAAE,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAA"}
1
+ {"version":3,"file":"access-boundary.js","sourceRoot":"","sources":["../src/access-boundary.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import { type SpecSource } from './axe-coverage.js';
2
+ import type { PayloadViolation } from './payload-source.js';
3
+ /** A custom admin view a Payload configuration declares, and where it declares it. */
4
+ export interface DeclaredAdminView {
5
+ readonly path: string;
6
+ readonly line: number;
7
+ }
8
+ /**
9
+ * The custom admin views a Payload configuration declares.
10
+ *
11
+ * Both `admin.components.views` and a collection's own are found, because both produce a route the
12
+ * pinned sweep will not reach and both are the project's markup.
13
+ * @param source the file's text.
14
+ * @returns one entry per declared view path, in the order the file declares them.
15
+ */
16
+ export declare const findDeclaredAdminViews: (source: string) => readonly DeclaredAdminView[];
17
+ /**
18
+ * The declared views that no specification scans for accessibility.
19
+ * @param views the views a configuration declares.
20
+ * @param specs the project's test files, which is where a scan would be written.
21
+ * @param everyFile every file a spec may import a scan from, the specs included.
22
+ * @returns one violation per view nothing looks at.
23
+ */
24
+ export declare const findUnscannedAdminViews: (views: readonly DeclaredAdminView[], specs: readonly SpecSource[], everyFile: readonly SpecSource[]) => readonly PayloadViolation[];
25
+ //# sourceMappingURL=admin-view-coverage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-view-coverage.d.ts","sourceRoot":"","sources":["../src/admin-view-coverage.ts"],"names":[],"mappings":"AAiBA,OAAO,EAA6B,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAC9E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AAG3D,sFAAsF;AACtF,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAsCD;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,GAAI,QAAQ,MAAM,KAAG,SAAS,iBAAiB,EAejF,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB,GAClC,OAAO,SAAS,iBAAiB,EAAE,EACnC,OAAO,SAAS,UAAU,EAAE,EAC5B,WAAW,SAAS,UAAU,EAAE,KAC/B,SAAS,gBAAgB,EA6BxB,CAAA"}
@@ -0,0 +1,90 @@
1
+ // Custom admin views, and the accessibility hole they sit in.
2
+ //
3
+ // The pinned sweep skips `/admin` unconditionally, and correctly: the panel is Payload's markup rather
4
+ // than the project's, and the crawl carries no credential to sign in with. A CUSTOM admin view is the
5
+ // one thing inside that exemption the project wrote itself, and nothing else in the harness will ever
6
+ // look at it. `jsx-a11y` reads the source, so it cannot see contrast, focus order, or a landmark whose
7
+ // role depends on where the framework's template put the markup - and that last one is not
8
+ // hypothetical: a bare `header` in a custom view becomes a second `banner` beside the panel's own,
9
+ // which is invisible until something renders it.
10
+ //
11
+ // ploaness cannot do the scanning. It cannot sign in, it does not know which container belongs to the
12
+ // project rather than to Payload, and a scan of the whole panel would report defects in components
13
+ // nobody working in that project can repair. What it can do is refuse to let the view go unlooked at.
14
+ // The configuration says a custom view exists; a spec has to say something scans it.
15
+ //
16
+ // This is the same shape as the anonymous-grant rule: the framework will happily leave a decision
17
+ // unmade, so the project is required to have made it somewhere a check can see.
18
+ import { containsRoute, reachesAxe } from './axe-coverage.js';
19
+ import { balancedArguments, lineOf, stripComments } from './source-text.js';
20
+ // `components` then `views`, each read as the block it opens rather than as text found anywhere in the
21
+ // file. Nesting the two is what tells Payload's own vocabulary apart from a project's: a `views` key
22
+ // that is not inside a `components` block is not a custom admin view.
23
+ const BLOCK_PATTERNS = [
24
+ String.raw `\bcomponents\s*:\s*\{`,
25
+ String.raw `\bviews\s*:\s*\{`,
26
+ ];
27
+ const VIEW_PATH = /\bpath\s*:\s*['"](\/[^'"]*)['"]/gu;
28
+ // The brace-delimited body opened at `at`, carrying the offset it starts at so that a match inside it
29
+ // can be reported at its line in the whole file rather than at its line in the fragment. The scan is
30
+ // `balancedArguments`, which skips string literals - a brace inside a quoted string would otherwise
31
+ // close the block early and hide every view declared after it.
32
+ const blockAt = (source, at) => {
33
+ const text = balancedArguments(source, at);
34
+ return text === undefined ? undefined : { text, start: at + 1 };
35
+ };
36
+ const blocksMatching = (blocks, pattern) => blocks.flatMap((block) => [...block.text.matchAll(new RegExp(pattern, 'gu'))].flatMap((match) => {
37
+ const opened = blockAt(block.text, match.index + match[0].length - 1);
38
+ return opened === undefined
39
+ ? []
40
+ : [{ text: opened.text, start: block.start + opened.start }];
41
+ }));
42
+ /**
43
+ * The custom admin views a Payload configuration declares.
44
+ *
45
+ * Both `admin.components.views` and a collection's own are found, because both produce a route the
46
+ * pinned sweep will not reach and both are the project's markup.
47
+ * @param source the file's text.
48
+ * @returns one entry per declared view path, in the order the file declares them.
49
+ */
50
+ export const findDeclaredAdminViews = (source) => {
51
+ const code = stripComments(source);
52
+ const views = BLOCK_PATTERNS.reduce((blocks, pattern) => blocksMatching(blocks, pattern), [{ text: code, start: 0 }]);
53
+ return views.flatMap((block) => [...block.text.matchAll(VIEW_PATH)].map((match) => ({
54
+ path: match[1] ?? '',
55
+ line: lineOf(code, block.start + match.index),
56
+ })));
57
+ };
58
+ /**
59
+ * The declared views that no specification scans for accessibility.
60
+ * @param views the views a configuration declares.
61
+ * @param specs the project's test files, which is where a scan would be written.
62
+ * @param everyFile every file a spec may import a scan from, the specs included.
63
+ * @returns one violation per view nothing looks at.
64
+ */
65
+ export const findUnscannedAdminViews = (views, specs, everyFile) => views.flatMap((view) => {
66
+ const driving = specs.filter((spec) => containsRoute(spec.source, view.path));
67
+ if (driving.length === 0) {
68
+ return [
69
+ {
70
+ line: view.line,
71
+ rule: 'admin-view-undriven',
72
+ reason: `no test drives the custom admin view at "${view.path}". It is this project's own ` +
73
+ 'markup inside the panel the accessibility sweep skips, so nothing else will ever look ' +
74
+ 'at it: give it an end-to-end spec that scans it with axe, scoped to the view container',
75
+ },
76
+ ];
77
+ }
78
+ return driving.some((spec) => reachesAxe(spec, everyFile))
79
+ ? []
80
+ : [
81
+ {
82
+ line: view.line,
83
+ rule: 'admin-view-unscanned',
84
+ reason: `the custom admin view at "${view.path}" is driven by a test but nothing scans it ` +
85
+ 'with axe. The pinned sweep skips the admin panel, so this view has no accessibility ' +
86
+ 'coverage at all: add a scan scoped to the view container, not to the whole panel',
87
+ },
88
+ ];
89
+ });
90
+ //# sourceMappingURL=admin-view-coverage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-view-coverage.js","sourceRoot":"","sources":["../src/admin-view-coverage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /** A file the coverage question is answered from: its repository path and its text. */
2
+ export interface SpecSource {
3
+ readonly path: string;
4
+ readonly source: string;
5
+ }
6
+ /**
7
+ * Whether a specification names one route.
8
+ *
9
+ * A route is a prefix of every longer route beneath it, so a bare `includes` would let a spec for
10
+ * `/calendar-archive` answer for `/calendar`. The character after the match has to end the route.
11
+ * @param source the specification's text.
12
+ * @param route the route being looked for.
13
+ * @returns true when the text drives that route and not merely one whose name starts the same way.
14
+ */
15
+ export declare const containsRoute: (source: string, route: string) => boolean;
16
+ /**
17
+ * Whether a specification BUILDS a URL beneath one route prefix.
18
+ *
19
+ * The evidence a dynamic route needs, and deliberately narrower than `containsRoute`. A spec cannot
20
+ * contain `/play/[id]`, because that is a file path rather than an address: what it contains is
21
+ * `/play/${gameId}`. Asking merely for the prefix would let a spec that visits `/play` answer for
22
+ * `/play/[id]`, which is the false pass this whole family of rules exists to avoid - so what is
23
+ * required is the prefix, a separator, and an interpolation, which together mean the spec computed an
24
+ * address rather than typed one.
25
+ * @param source the specification's text.
26
+ * @param prefix the static part of the route, before its first dynamic segment.
27
+ * @returns true when the text builds a URL below that prefix.
28
+ */
29
+ export declare const containsBuiltRoute: (source: string, prefix: string) => boolean;
30
+ /**
31
+ * Whether a specification scans with axe, directly or through a helper it imports.
32
+ *
33
+ * One hop, deliberately. A spec that scans through a helper is the ordinary arrangement and has to be
34
+ * recognised; a chain deeper than that is a project hiding its own scan from itself, and following it
35
+ * would trade a rule anyone can predict for one nobody can.
36
+ * @param spec the specification being judged.
37
+ * @param everyFile every file it may import a scan from, the specifications included.
38
+ * @returns true when running that spec reaches axe.
39
+ */
40
+ export declare const reachesAxe: (spec: SpecSource, everyFile: readonly SpecSource[]) => boolean;
41
+ //# sourceMappingURL=axe-coverage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"axe-coverage.d.ts","sourceRoot":"","sources":["../src/axe-coverage.ts"],"names":[],"mappings":"AAgBA,uFAAuF;AACvF,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AA0CD;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,GAAI,QAAQ,MAAM,EAAE,OAAO,MAAM,KAAG,OACmB,CAAA;AAEjF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,kBAAkB,GAAI,QAAQ,MAAM,EAAE,QAAQ,MAAM,KAAG,OACD,CAAA;AA4BnE;;;;;;;;;GASG;AACH,eAAO,MAAM,UAAU,GAAI,MAAM,UAAU,EAAE,WAAW,SAAS,UAAU,EAAE,KAAG,OAI7E,CAAA"}