@coderifts/agent-guard 6.2.0 → 6.4.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 (100) hide show
  1. package/README.md +36 -10
  2. package/dist/cjs/cas-adapters/api.d.ts +94 -0
  3. package/dist/cjs/cas-adapters/api.d.ts.map +1 -0
  4. package/dist/cjs/cas-adapters/api.js +120 -0
  5. package/dist/cjs/cas-adapters/api.js.map +1 -0
  6. package/dist/cjs/cas-adapters/db.d.ts +93 -0
  7. package/dist/cjs/cas-adapters/db.d.ts.map +1 -0
  8. package/dist/cjs/cas-adapters/db.js +139 -0
  9. package/dist/cjs/cas-adapters/db.js.map +1 -0
  10. package/dist/cjs/cas-adapters/registry.d.ts +88 -0
  11. package/dist/cjs/cas-adapters/registry.d.ts.map +1 -0
  12. package/dist/cjs/cas-adapters/registry.js +120 -0
  13. package/dist/cjs/cas-adapters/registry.js.map +1 -0
  14. package/dist/cjs/cas-attestation.d.ts +94 -0
  15. package/dist/cjs/cas-attestation.d.ts.map +1 -0
  16. package/dist/cjs/cas-attestation.js +133 -0
  17. package/dist/cjs/cas-attestation.js.map +1 -0
  18. package/dist/cjs/deploy-gate.d.ts +10 -1
  19. package/dist/cjs/deploy-gate.d.ts.map +1 -1
  20. package/dist/cjs/deploy-gate.js +14 -10
  21. package/dist/cjs/deploy-gate.js.map +1 -1
  22. package/dist/cjs/execute-tool-call.d.ts +100 -0
  23. package/dist/cjs/execute-tool-call.d.ts.map +1 -0
  24. package/dist/cjs/execute-tool-call.js +334 -0
  25. package/dist/cjs/execute-tool-call.js.map +1 -0
  26. package/dist/cjs/execution-time-fingerprint.d.ts +8 -0
  27. package/dist/cjs/execution-time-fingerprint.d.ts.map +1 -1
  28. package/dist/cjs/execution-time-fingerprint.js +13 -1
  29. package/dist/cjs/execution-time-fingerprint.js.map +1 -1
  30. package/dist/cjs/guard.d.ts.map +1 -1
  31. package/dist/cjs/guard.js +28 -9
  32. package/dist/cjs/guard.js.map +1 -1
  33. package/dist/cjs/index.d.ts +13 -1
  34. package/dist/cjs/index.d.ts.map +1 -1
  35. package/dist/cjs/index.js +51 -2
  36. package/dist/cjs/index.js.map +1 -1
  37. package/dist/cjs/merge-gate.d.ts +8 -0
  38. package/dist/cjs/merge-gate.d.ts.map +1 -1
  39. package/dist/cjs/merge-gate.js +19 -12
  40. package/dist/cjs/merge-gate.js.map +1 -1
  41. package/dist/cjs/remediation-loop-attestation.d.ts +125 -0
  42. package/dist/cjs/remediation-loop-attestation.d.ts.map +1 -0
  43. package/dist/cjs/remediation-loop-attestation.js +186 -0
  44. package/dist/cjs/remediation-loop-attestation.js.map +1 -0
  45. package/dist/cjs/types.d.ts +20 -3
  46. package/dist/cjs/types.d.ts.map +1 -1
  47. package/dist/cjs/with-coderifts.d.ts +9 -0
  48. package/dist/cjs/with-coderifts.d.ts.map +1 -1
  49. package/dist/cjs/with-coderifts.js +3 -0
  50. package/dist/cjs/with-coderifts.js.map +1 -1
  51. package/dist/esm/cas-adapters/api.d.ts +94 -0
  52. package/dist/esm/cas-adapters/api.d.ts.map +1 -0
  53. package/dist/esm/cas-adapters/api.js +116 -0
  54. package/dist/esm/cas-adapters/api.js.map +1 -0
  55. package/dist/esm/cas-adapters/db.d.ts +93 -0
  56. package/dist/esm/cas-adapters/db.d.ts.map +1 -0
  57. package/dist/esm/cas-adapters/db.js +135 -0
  58. package/dist/esm/cas-adapters/db.js.map +1 -0
  59. package/dist/esm/cas-adapters/registry.d.ts +88 -0
  60. package/dist/esm/cas-adapters/registry.d.ts.map +1 -0
  61. package/dist/esm/cas-adapters/registry.js +116 -0
  62. package/dist/esm/cas-adapters/registry.js.map +1 -0
  63. package/dist/esm/cas-attestation.d.ts +94 -0
  64. package/dist/esm/cas-attestation.d.ts.map +1 -0
  65. package/dist/esm/cas-attestation.js +127 -0
  66. package/dist/esm/cas-attestation.js.map +1 -0
  67. package/dist/esm/deploy-gate.d.ts +10 -1
  68. package/dist/esm/deploy-gate.d.ts.map +1 -1
  69. package/dist/esm/deploy-gate.js +14 -10
  70. package/dist/esm/deploy-gate.js.map +1 -1
  71. package/dist/esm/execute-tool-call.d.ts +100 -0
  72. package/dist/esm/execute-tool-call.d.ts.map +1 -0
  73. package/dist/esm/execute-tool-call.js +325 -0
  74. package/dist/esm/execute-tool-call.js.map +1 -0
  75. package/dist/esm/execution-time-fingerprint.d.ts +8 -0
  76. package/dist/esm/execution-time-fingerprint.d.ts.map +1 -1
  77. package/dist/esm/execution-time-fingerprint.js +11 -0
  78. package/dist/esm/execution-time-fingerprint.js.map +1 -1
  79. package/dist/esm/guard.d.ts.map +1 -1
  80. package/dist/esm/guard.js +29 -10
  81. package/dist/esm/guard.js.map +1 -1
  82. package/dist/esm/index.d.ts +13 -1
  83. package/dist/esm/index.d.ts.map +1 -1
  84. package/dist/esm/index.js +16 -1
  85. package/dist/esm/index.js.map +1 -1
  86. package/dist/esm/merge-gate.d.ts +8 -0
  87. package/dist/esm/merge-gate.d.ts.map +1 -1
  88. package/dist/esm/merge-gate.js +19 -12
  89. package/dist/esm/merge-gate.js.map +1 -1
  90. package/dist/esm/remediation-loop-attestation.d.ts +125 -0
  91. package/dist/esm/remediation-loop-attestation.d.ts.map +1 -0
  92. package/dist/esm/remediation-loop-attestation.js +179 -0
  93. package/dist/esm/remediation-loop-attestation.js.map +1 -0
  94. package/dist/esm/types.d.ts +20 -3
  95. package/dist/esm/types.d.ts.map +1 -1
  96. package/dist/esm/with-coderifts.d.ts +9 -0
  97. package/dist/esm/with-coderifts.d.ts.map +1 -1
  98. package/dist/esm/with-coderifts.js +3 -0
  99. package/dist/esm/with-coderifts.js.map +1 -1
  100. package/package.json +1 -1
package/README.md CHANGED
@@ -67,23 +67,40 @@ requireExecutionStateMatch?: boolean | 'warn';
67
67
  | Mode | Value | Behavior |
68
68
  |---|---|---|
69
69
  | **OFF** (default) | `false` / absent | No recheck. Residual execution-state race as described under “TOCTOU is unclosed.” |
70
- | **Warn** (report-only) | `'warn'` | Rechecks. On **drift**, emits `execution_state_drift_observed` via **`onEvent`**, then **proceeds** (does not block). On match, silent (drift-only telemetry). Use this to gather production drift data without changing who executes. |
71
- | **Enforce** | `true` | Rechecks. On drift, **blocks** with integrity cause `EXECUTION_STATE_DRIFT` (factory never runs). Byte-identical to the original enforce path. |
70
+ | **Warn** (report-only) | `'warn'` | Rechecks. On **real drift**, emits loud `execution_state_drift_observed` via **`onEvent`**, then **proceeds**. On **unmeasurable** state (nothing to compare), emits quiet `execution_state_unmeasurable` (not drift, not safety), then **proceeds**. On match, silent. |
71
+ | **Enforce** | `true` | Rechecks. On mismatch, **blocks** with integrity cause `EXECUTION_STATE_DRIFT` (factory never runs). Byte-identical to the original enforce path. |
72
72
 
73
- Recommended adoption path: **off → `'warn'` (observe) `true` (enforce)** once your own
74
- telemetry shows drift is rare and real. A default flip to enforce is a later, versioned
75
- product decision — not this mode’s job.
73
+ **Two warn signals (noise-fix for a future default flip):**
76
74
 
77
- Warn-mode event (host-side only; the guard does **not** phone home), on `GuardConfig.onEvent`:
75
+ | Event | When | Meaning |
76
+ |---|---|---|
77
+ | `execution_state_drift_observed` (loud) | `reason === fingerprint_stale_at_execute` | Authorized fp ≠ current artifacts hash — real T1→T2 content drift. |
78
+ | `execution_state_unmeasurable` (quiet) | `missing_artifacts` or `missing_authorized_fingerprint` | Nothing to measure — **not** evidence of drift and **not** evidence of safety. |
79
+
80
+ A future default flip to warn (step3b, guard@7) will ride on the **loud** signal only; quiet unmeasurable must not page as drift. Recommended adoption: **off → `'warn'` (observe loud only) → `true` (enforce)**.
81
+
82
+ Warn-mode events (host-side only; the guard does **not** phone home), on `GuardConfig.onEvent`:
78
83
 
79
84
  ```ts
85
+ // Loud — real drift (shape frozen; regression-locked)
80
86
  {
81
87
  type: 'execution_state_drift_observed';
82
88
  at: string; // ISO timestamp
83
89
  decisionId?: string;
84
90
  current_fingerprint: string | null;
85
91
  authorized_fingerprint: string | null;
86
- reason: string;
92
+ reason: string; // fingerprint_stale_at_execute
93
+ }
94
+
95
+ // Quiet — unmeasurable (additive)
96
+ {
97
+ type: 'execution_state_unmeasurable';
98
+ at: string;
99
+ decisionId?: string;
100
+ current_fingerprint: string | null;
101
+ authorized_fingerprint: string | null;
102
+ reason: string; // missing_artifacts | missing_authorized_fingerprint
103
+ note: 'nothing to measure — not evidence of drift, not evidence of safety';
87
104
  }
88
105
  ```
89
106
 
@@ -94,9 +111,10 @@ const outcome = await guardToolCall(call, executeFactory, {
94
111
  requireExecutionStateMatch: 'warn', // opt-in telemetry; still proceeds on drift
95
112
  onEvent: (e) => {
96
113
  if (e.type === 'execution_state_drift_observed') {
97
- // host metrics / log — you own retention; nothing is sent to CodeRifts from here
114
+ // host metrics / log — real drift only; nothing is sent to CodeRifts from here
98
115
  console.warn('execution-state drift', e.decisionId, e.reason, e.current_fingerprint);
99
116
  }
117
+ // optional: e.type === 'execution_state_unmeasurable' → debug-only, do not page
100
118
  },
101
119
  });
102
120
  ```
@@ -147,6 +165,13 @@ const { tools, registry_report, composition_assurance } = withCodeRifts({
147
165
  // the guard — the composition can only protect the table it returns.
148
166
  ```
149
167
 
168
+ Optional guard-policy fields on the same input are forwarded **unchanged** onto `GuardConfig` when
169
+ present (absent = today’s defaults — no behavior change): `onEvent`, `monitoringSinkWired`,
170
+ `resolvePriorContent`, `requireFreshness`, `allowStaleContext`, `requireConditionalWrite`, and
171
+ **`requireExecutionStateMatch`** (`boolean | 'warn'`, default off). The last is plumbing for the
172
+ ID842 T2 recheck so one-call hosts can opt into warn/true; it is **not** TOCTOU closure and does
173
+ not flip the package default (see **`requireExecutionStateMatch`** above).
174
+
150
175
  ### Final-answer proof block (ID645)
151
176
 
152
177
  When a call produces a machine `GuardExecutionProof` (`outcome.proof`), you can embed a
@@ -739,8 +764,9 @@ Pass both on `withCodeRifts({ …, onEvent, monitoringSinkWired: true })`, or on
739
764
  host commits. Closing that needs a host-side conditional write (compare-and-swap on a version
740
765
  token) — this package never writes, and reports `conditional_write` as a host assertion it
741
766
  cannot verify. Opt-in **`requireExecutionStateMatch`** (`false` / `'warn'` / `true`) can
742
- recheck the execution-time fingerprint before the factory (warn = telemetry via
743
- `execution_state_drift_observed`; true = block with `EXECUTION_STATE_DRIFT`) — a detection or
767
+ recheck the execution-time fingerprint before the factory (warn = loud
768
+ `execution_state_drift_observed` on real drift / quiet `execution_state_unmeasurable` when
769
+ nothing to measure; true = block with `EXECUTION_STATE_DRIFT`) — a detection or
744
770
  enforcement aid, **not** a full TOCTOU closure. See the subsection above.
745
771
  - **Retry-safe** — `executionAttempted` is the only safe-to-retry signal; a post-authorization throw
746
772
  is `executionAttempted:true` (the remote side effect may have landed).
@@ -0,0 +1,94 @@
1
+ /**
2
+ * HTTP/API CAS adapter — ETag (If-Match) token discipline over host-injected I/O.
3
+ *
4
+ * Uses executeIfUnchanged (conditional-write.ts). Does not invent a parallel outcome shape.
5
+ * Token format is opaque to the guard (string equality only).
6
+ *
7
+ * Token encoding (this adapter only; not interpreted by the core):
8
+ * api:v1:<raw_etag_or_version_header_value>
9
+ * Missing / empty ETag → API_ABSENT_TOKEN.
10
+ *
11
+ * HONESTY: this adapter does NOT perform HTTP. The host callback MUST send If-Match (or
12
+ * equivalent) itself. The adapter only normalizes tokens and maps the host-reported
13
+ * {committed|precondition_failed} onto ExecuteIfUnchangedOutcome. Same class as
14
+ * conditional_write_is_host_asserted_not_cas_verified — the package never claims it sent
15
+ * the header.
16
+ *
17
+ * No fetch/undici — all I/O is host-injected.
18
+ */
19
+ import { tokensEqual, type ExecuteIfUnchangedOutcome, type VersionToken } from '../conditional-write.js';
20
+ /** Prefix for this adapter's opaque tokens (equality-only outside this module). */
21
+ export declare const API_VERSION_TOKEN_PREFIX = "api:v1:";
22
+ /**
23
+ * Token used when the resource has no ETag / version header yet (or host reported null).
24
+ * Hosts that measured absence should pass this as expected_token for create-if-absent CAS.
25
+ */
26
+ export declare const API_ABSENT_TOKEN: VersionToken;
27
+ /**
28
+ * Build a VersionToken from a host-supplied ETag or version-header value.
29
+ * null / undefined / empty / whitespace-only → API_ABSENT_TOKEN.
30
+ * Surrounding weak/strong quote marks are stripped once for stable equality.
31
+ */
32
+ export declare function createApiVersionToken(etag: string | null | undefined): VersionToken;
33
+ /** Raw value embedded in an api:v1: token (for host If-Match headers). Absent → null. */
34
+ export declare function apiTokenRaw(token: VersionToken | null | undefined): string | null;
35
+ /**
36
+ * Host report after attempting a conditional write.
37
+ * The host is responsible for sending If-Match / If-None-Match / equivalent.
38
+ */
39
+ export type ApiHostWriteReport<T = unknown> = {
40
+ status: 'committed';
41
+ /** New ETag after write when the server returned one; null/omit when unknown. */
42
+ new_etag?: string | null;
43
+ result?: T;
44
+ } | {
45
+ status: 'precondition_failed';
46
+ /** Current ETag if the host observed one (e.g. from 412 body / re-GET); omit → null. */
47
+ current_etag?: string | null;
48
+ };
49
+ export type WriteApiIfUnchangedArgs<T = unknown> = {
50
+ /** Token measured before the host decided to write (from createApiVersionToken). */
51
+ expected_token: VersionToken;
52
+ /**
53
+ * Re-read the resource ETag (or equivalent version header).
54
+ * Called by executeIfUnchanged before write and again after write when detect is on.
55
+ */
56
+ current_etag: () => string | null | Promise<string | null>;
57
+ /**
58
+ * Host performs the conditional HTTP write.
59
+ * `if_match` is the raw expected ETag for the If-Match header (null when ABSENT).
60
+ * Adapter does not send the request — host must.
61
+ */
62
+ write: (ctx: {
63
+ if_match: string | null;
64
+ expected_token: VersionToken;
65
+ }) => ApiHostWriteReport<T> | Promise<ApiHostWriteReport<T>>;
66
+ /**
67
+ * Opt-in post-commit detection (default false — byte-identical when off).
68
+ * Requires a meaningful intended post-ETag: when the host omits new_etag on success,
69
+ * detection is a no-op (cannot know intended post state without inventing tokens).
70
+ */
71
+ detect_stale_during_commit?: boolean;
72
+ };
73
+ export type ApiWriteResult<T = unknown> = {
74
+ new_etag: string | null;
75
+ result: T | undefined;
76
+ };
77
+ /**
78
+ * Conditional API write: re-read ETag; if token matches expected → host write with If-Match context.
79
+ *
80
+ * Mapping (host report → ExecuteIfUnchangedOutcome):
81
+ * committed (+ optional new_etag) → committed (version_token = expected; result carries new_etag)
82
+ * precondition_failed (412 class) → refused / stale_version_token
83
+ * current_token from current_etag when host supplied it,
84
+ * else null (no invented token)
85
+ *
86
+ * Missing new_etag on success: stored as null on result; detect_stale_during_commit is a no-op
87
+ * unless new_etag is a non-empty string (honest — no ABSENT fabrication as "expected after").
88
+ *
89
+ * Wired through executeIfUnchanged — not a parallel path.
90
+ */
91
+ export declare function writeApiIfUnchanged<T = unknown>(args: WriteApiIfUnchangedArgs<T>): Promise<ExecuteIfUnchangedOutcome<ApiWriteResult<T>>>;
92
+ /** Re-export equality helper for hosts comparing tokens without importing core module. */
93
+ export { tokensEqual };
94
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../src/cas-adapters/api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,OAAO,EAGL,WAAW,EACX,KAAK,yBAAyB,EAC9B,KAAK,YAAY,EAClB,MAAM,yBAAyB,CAAC;AAEjC,mFAAmF;AACnF,eAAO,MAAM,wBAAwB,YAAY,CAAC;AAElD;;;GAGG;AACH,eAAO,MAAM,gBAAgB,EAAE,YAA8B,CAAC;AAE9D;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC9B,YAAY,CAWd;AAED,yFAAyF;AACzF,wBAAgB,WAAW,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAKjF;AAED;;;GAGG;AACH,MAAM,MAAM,kBAAkB,CAAC,CAAC,GAAG,OAAO,IACtC;IACE,MAAM,EAAE,WAAW,CAAC;IACpB,iFAAiF;IACjF,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,CAAC,EAAE,CAAC,CAAC;CACZ,GACD;IACE,MAAM,EAAE,qBAAqB,CAAC;IAC9B,wFAAwF;IACxF,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B,CAAC;AAEN,MAAM,MAAM,uBAAuB,CAAC,CAAC,GAAG,OAAO,IAAI;IACjD,oFAAoF;IACpF,cAAc,EAAE,YAAY,CAAC;IAC7B;;;OAGG;IACH,YAAY,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC3D;;;;OAIG;IACH,KAAK,EAAE,CAAC,GAAG,EAAE;QACX,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QACxB,cAAc,EAAE,YAAY,CAAC;KAC9B,KAAK,kBAAkB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7D;;;;OAIG;IACH,0BAA0B,CAAC,EAAE,OAAO,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,cAAc,CAAC,CAAC,GAAG,OAAO,IAAI;IACxC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,CAAC,GAAG,SAAS,CAAC;CACvB,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,wBAAsB,mBAAmB,CAAC,CAAC,GAAG,OAAO,EACnD,IAAI,EAAE,uBAAuB,CAAC,CAAC,CAAC,GAC/B,OAAO,CAAC,yBAAyB,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CA8CvD;AAED,0FAA0F;AAC1F,OAAO,EAAE,WAAW,EAAE,CAAC"}
@@ -0,0 +1,120 @@
1
+ /**
2
+ * HTTP/API CAS adapter — ETag (If-Match) token discipline over host-injected I/O.
3
+ *
4
+ * Uses executeIfUnchanged (conditional-write.ts). Does not invent a parallel outcome shape.
5
+ * Token format is opaque to the guard (string equality only).
6
+ *
7
+ * Token encoding (this adapter only; not interpreted by the core):
8
+ * api:v1:<raw_etag_or_version_header_value>
9
+ * Missing / empty ETag → API_ABSENT_TOKEN.
10
+ *
11
+ * HONESTY: this adapter does NOT perform HTTP. The host callback MUST send If-Match (or
12
+ * equivalent) itself. The adapter only normalizes tokens and maps the host-reported
13
+ * {committed|precondition_failed} onto ExecuteIfUnchangedOutcome. Same class as
14
+ * conditional_write_is_host_asserted_not_cas_verified — the package never claims it sent
15
+ * the header.
16
+ *
17
+ * No fetch/undici — all I/O is host-injected.
18
+ */
19
+ 'use strict';
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.tokensEqual = exports.API_ABSENT_TOKEN = exports.API_VERSION_TOKEN_PREFIX = void 0;
22
+ exports.createApiVersionToken = createApiVersionToken;
23
+ exports.apiTokenRaw = apiTokenRaw;
24
+ exports.writeApiIfUnchanged = writeApiIfUnchanged;
25
+ const conditional_write_js_1 = require("../conditional-write.js");
26
+ Object.defineProperty(exports, "tokensEqual", { enumerable: true, get: function () { return conditional_write_js_1.tokensEqual; } });
27
+ /** Prefix for this adapter's opaque tokens (equality-only outside this module). */
28
+ exports.API_VERSION_TOKEN_PREFIX = 'api:v1:';
29
+ /**
30
+ * Token used when the resource has no ETag / version header yet (or host reported null).
31
+ * Hosts that measured absence should pass this as expected_token for create-if-absent CAS.
32
+ */
33
+ exports.API_ABSENT_TOKEN = 'api:v1:absent';
34
+ /**
35
+ * Build a VersionToken from a host-supplied ETag or version-header value.
36
+ * null / undefined / empty / whitespace-only → API_ABSENT_TOKEN.
37
+ * Surrounding weak/strong quote marks are stripped once for stable equality.
38
+ */
39
+ function createApiVersionToken(etag) {
40
+ if (etag == null)
41
+ return exports.API_ABSENT_TOKEN;
42
+ let s = String(etag).trim();
43
+ if (s.length === 0)
44
+ return exports.API_ABSENT_TOKEN;
45
+ // Strip one layer of optional W/ and quotes: W/"abc" → abc, "abc" → abc
46
+ if (s.startsWith('W/') || s.startsWith('w/'))
47
+ s = s.slice(2).trim();
48
+ if (s.length >= 2 && s.startsWith('"') && s.endsWith('"')) {
49
+ s = s.slice(1, -1);
50
+ }
51
+ if (s.length === 0)
52
+ return exports.API_ABSENT_TOKEN;
53
+ return `${exports.API_VERSION_TOKEN_PREFIX}${s}`;
54
+ }
55
+ /** Raw value embedded in an api:v1: token (for host If-Match headers). Absent → null. */
56
+ function apiTokenRaw(token) {
57
+ if (typeof token !== 'string' || !token.startsWith(exports.API_VERSION_TOKEN_PREFIX))
58
+ return null;
59
+ if (token === exports.API_ABSENT_TOKEN)
60
+ return null;
61
+ const raw = token.slice(exports.API_VERSION_TOKEN_PREFIX.length);
62
+ return raw.length > 0 ? raw : null;
63
+ }
64
+ /**
65
+ * Conditional API write: re-read ETag; if token matches expected → host write with If-Match context.
66
+ *
67
+ * Mapping (host report → ExecuteIfUnchangedOutcome):
68
+ * committed (+ optional new_etag) → committed (version_token = expected; result carries new_etag)
69
+ * precondition_failed (412 class) → refused / stale_version_token
70
+ * current_token from current_etag when host supplied it,
71
+ * else null (no invented token)
72
+ *
73
+ * Missing new_etag on success: stored as null on result; detect_stale_during_commit is a no-op
74
+ * unless new_etag is a non-empty string (honest — no ABSENT fabrication as "expected after").
75
+ *
76
+ * Wired through executeIfUnchanged — not a parallel path.
77
+ */
78
+ async function writeApiIfUnchanged(args) {
79
+ const detect = args.detect_stale_during_commit === true;
80
+ return (0, conditional_write_js_1.executeIfUnchanged)({
81
+ expected_token: args.expected_token,
82
+ current_token: async () => createApiVersionToken(await args.current_etag()),
83
+ detect_stale_during_commit: detect,
84
+ expected_after_commit: detect
85
+ ? async (written) => {
86
+ if (typeof written.new_etag === 'string' && written.new_etag.trim().length > 0) {
87
+ return createApiVersionToken(written.new_etag);
88
+ }
89
+ // No intended post-ETag: return live re-read so tokensEqual is tautological (detect no-op).
90
+ return createApiVersionToken(await args.current_etag());
91
+ }
92
+ : undefined,
93
+ write: async () => {
94
+ const report = await args.write({
95
+ if_match: apiTokenRaw(args.expected_token),
96
+ expected_token: args.expected_token,
97
+ });
98
+ if (!report || typeof report !== 'object') {
99
+ throw new Error('writeApiIfUnchanged: host write must return ApiHostWriteReport');
100
+ }
101
+ if (report.status === 'precondition_failed') {
102
+ const cur = report.current_etag !== undefined
103
+ ? createApiVersionToken(report.current_etag)
104
+ : null;
105
+ throw new conditional_write_js_1.StaleVersionTokenAbort(cur);
106
+ }
107
+ if (report.status !== 'committed') {
108
+ throw new Error(`writeApiIfUnchanged: unknown host report status ${String(report.status)}`);
109
+ }
110
+ const new_etag = report.new_etag === undefined || report.new_etag === null
111
+ ? null
112
+ : String(report.new_etag);
113
+ return {
114
+ new_etag,
115
+ result: report.result,
116
+ };
117
+ },
118
+ });
119
+ }
120
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../../../src/cas-adapters/api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Database optimistic-lock CAS adapter — version-column / rowversion discipline over host-injected I/O.
3
+ *
4
+ * Uses executeIfUnchanged (conditional-write.ts). Does not invent a parallel outcome shape.
5
+ * Token format is opaque to the guard (string equality only).
6
+ *
7
+ * Token encoding (this adapter only; not interpreted by the core):
8
+ * db:v1:<version_column_value>
9
+ * Missing / empty version → DB_ABSENT_TOKEN.
10
+ *
11
+ * HONESTY: this adapter never composes SQL and never opens a connection. The host callback
12
+ * performs the UPDATE … WHERE version = $expected (or equivalent) and reports rows_affected
13
+ * or an explicit committed/conflict shape. Same honesty class as host-asserted conditional write.
14
+ *
15
+ * No pg/mysql/sqlite drivers — all I/O is host-injected.
16
+ */
17
+ import { tokensEqual, type ExecuteIfUnchangedOutcome, type VersionToken } from '../conditional-write.js';
18
+ /** Prefix for this adapter's opaque tokens (equality-only outside this module). */
19
+ export declare const DB_VERSION_TOKEN_PREFIX = "db:v1:";
20
+ /**
21
+ * Token used when the row has no version yet (insert path) or host reported null.
22
+ * Hosts that measured absence should pass this as expected_token for first write.
23
+ */
24
+ export declare const DB_ABSENT_TOKEN: VersionToken;
25
+ /**
26
+ * Build a VersionToken from a host-read version column / rowversion / xmin-style value.
27
+ * null / undefined / empty → DB_ABSENT_TOKEN. Numbers are stringified (no format invention).
28
+ */
29
+ export declare function createDbVersionToken(version: string | number | bigint | null | undefined): VersionToken;
30
+ /** Raw version embedded in a db:v1: token (for host SQL bind params). Absent → null. */
31
+ export declare function dbTokenRaw(token: VersionToken | null | undefined): string | null;
32
+ /**
33
+ * Host report after attempting an optimistic-lock write.
34
+ * Prefer rows_affected (UPDATE … WHERE version = $expected) or explicit committed/conflict.
35
+ */
36
+ export type DbHostWriteReport<T = unknown> = {
37
+ status: 'committed';
38
+ /** Version after write when the host can report it; null/omit when unknown. */
39
+ new_version?: string | number | bigint | null;
40
+ result?: T;
41
+ } | {
42
+ /** Optimistic lock lost — zero rows updated. */
43
+ status: 'conflict';
44
+ /** Current version if the host re-read it; omit → null (no invented token). */
45
+ current_version?: string | number | bigint | null;
46
+ } | {
47
+ /**
48
+ * Alternate committed shape: rows_affected > 0 means committed.
49
+ * rows_affected === 0 means conflict (mapped to refused).
50
+ */
51
+ rows_affected: number;
52
+ new_version?: string | number | bigint | null;
53
+ result?: T;
54
+ };
55
+ export type WriteDbIfUnchangedArgs<T = unknown> = {
56
+ /** Token measured before the host decided to write (from createDbVersionToken). */
57
+ expected_token: VersionToken;
58
+ /** Re-read the row version column. Called pre-write and post-write when detect is on. */
59
+ current_version: () => string | number | bigint | null | Promise<string | number | bigint | null>;
60
+ /**
61
+ * Host performs the optimistic-lock mutation (SQL/query builder — never composed here).
62
+ * Receives the raw expected version for the WHERE clause bind.
63
+ */
64
+ write: (ctx: {
65
+ expected_version: string | null;
66
+ expected_token: VersionToken;
67
+ }) => DbHostWriteReport<T> | Promise<DbHostWriteReport<T>>;
68
+ /**
69
+ * Opt-in post-commit detection (default false).
70
+ * When host omits new_version on success, detection is a no-op (cannot know intended post state).
71
+ */
72
+ detect_stale_during_commit?: boolean;
73
+ };
74
+ export type DbWriteResult<T = unknown> = {
75
+ new_version: string | null;
76
+ result: T | undefined;
77
+ rows_affected?: number;
78
+ };
79
+ /**
80
+ * Conditional DB write: re-read version; if token matches → host optimistic-lock write.
81
+ *
82
+ * Mapping (host report → ExecuteIfUnchangedOutcome):
83
+ * committed / rows_affected > 0 → committed
84
+ * conflict / rows_affected === 0 → refused / stale_version_token
85
+ * current_token from current_version when supplied,
86
+ * else null (no invented token)
87
+ *
88
+ * Wired through executeIfUnchanged — not a parallel path. Never composes SQL.
89
+ */
90
+ export declare function writeDbIfUnchanged<T = unknown>(args: WriteDbIfUnchangedArgs<T>): Promise<ExecuteIfUnchangedOutcome<DbWriteResult<T>>>;
91
+ /** Re-export equality helper for hosts comparing tokens without importing core module. */
92
+ export { tokensEqual };
93
+ //# sourceMappingURL=db.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../../src/cas-adapters/db.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,EAGL,WAAW,EACX,KAAK,yBAAyB,EAC9B,KAAK,YAAY,EAClB,MAAM,yBAAyB,CAAC;AAEjC,mFAAmF;AACnF,eAAO,MAAM,uBAAuB,WAAW,CAAC;AAEhD;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,YAA6B,CAAC;AAE5D;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,GACnD,YAAY,CAMd;AAED,wFAAwF;AACxF,wBAAgB,UAAU,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAKhF;AAED;;;GAGG;AACH,MAAM,MAAM,iBAAiB,CAAC,CAAC,GAAG,OAAO,IACrC;IACE,MAAM,EAAE,WAAW,CAAC;IACpB,+EAA+E;IAC/E,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC9C,MAAM,CAAC,EAAE,CAAC,CAAC;CACZ,GACD;IACE,gDAAgD;IAChD,MAAM,EAAE,UAAU,CAAC;IACnB,+EAA+E;IAC/E,eAAe,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;CACnD,GACD;IACE;;;OAGG;IACH,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC9C,MAAM,CAAC,EAAE,CAAC,CAAC;CACZ,CAAC;AAEN,MAAM,MAAM,sBAAsB,CAAC,CAAC,GAAG,OAAO,IAAI;IAChD,mFAAmF;IACnF,cAAc,EAAE,YAAY,CAAC;IAC7B,yFAAyF;IACzF,eAAe,EAAE,MACb,MAAM,GACN,MAAM,GACN,MAAM,GACN,IAAI,GACJ,OAAO,CAAC,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7C;;;OAGG;IACH,KAAK,EAAE,CAAC,GAAG,EAAE;QACX,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;QAChC,cAAc,EAAE,YAAY,CAAC;KAC9B,KAAK,iBAAiB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3D;;;OAGG;IACH,0BAA0B,CAAC,EAAE,OAAO,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,aAAa,CAAC,CAAC,GAAG,OAAO,IAAI;IACvC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,CAAC,GAAG,SAAS,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAwDF;;;;;;;;;;GAUG;AACH,wBAAsB,kBAAkB,CAAC,CAAC,GAAG,OAAO,EAClD,IAAI,EAAE,sBAAsB,CAAC,CAAC,CAAC,GAC9B,OAAO,CAAC,yBAAyB,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAuCtD;AAED,0FAA0F;AAC1F,OAAO,EAAE,WAAW,EAAE,CAAC"}
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Database optimistic-lock CAS adapter — version-column / rowversion discipline over host-injected I/O.
3
+ *
4
+ * Uses executeIfUnchanged (conditional-write.ts). Does not invent a parallel outcome shape.
5
+ * Token format is opaque to the guard (string equality only).
6
+ *
7
+ * Token encoding (this adapter only; not interpreted by the core):
8
+ * db:v1:<version_column_value>
9
+ * Missing / empty version → DB_ABSENT_TOKEN.
10
+ *
11
+ * HONESTY: this adapter never composes SQL and never opens a connection. The host callback
12
+ * performs the UPDATE … WHERE version = $expected (or equivalent) and reports rows_affected
13
+ * or an explicit committed/conflict shape. Same honesty class as host-asserted conditional write.
14
+ *
15
+ * No pg/mysql/sqlite drivers — all I/O is host-injected.
16
+ */
17
+ 'use strict';
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.tokensEqual = exports.DB_ABSENT_TOKEN = exports.DB_VERSION_TOKEN_PREFIX = void 0;
20
+ exports.createDbVersionToken = createDbVersionToken;
21
+ exports.dbTokenRaw = dbTokenRaw;
22
+ exports.writeDbIfUnchanged = writeDbIfUnchanged;
23
+ const conditional_write_js_1 = require("../conditional-write.js");
24
+ Object.defineProperty(exports, "tokensEqual", { enumerable: true, get: function () { return conditional_write_js_1.tokensEqual; } });
25
+ /** Prefix for this adapter's opaque tokens (equality-only outside this module). */
26
+ exports.DB_VERSION_TOKEN_PREFIX = 'db:v1:';
27
+ /**
28
+ * Token used when the row has no version yet (insert path) or host reported null.
29
+ * Hosts that measured absence should pass this as expected_token for first write.
30
+ */
31
+ exports.DB_ABSENT_TOKEN = 'db:v1:absent';
32
+ /**
33
+ * Build a VersionToken from a host-read version column / rowversion / xmin-style value.
34
+ * null / undefined / empty → DB_ABSENT_TOKEN. Numbers are stringified (no format invention).
35
+ */
36
+ function createDbVersionToken(version) {
37
+ if (version == null)
38
+ return exports.DB_ABSENT_TOKEN;
39
+ if (typeof version === 'number' && !Number.isFinite(version))
40
+ return exports.DB_ABSENT_TOKEN;
41
+ const s = String(version).trim();
42
+ if (s.length === 0)
43
+ return exports.DB_ABSENT_TOKEN;
44
+ return `${exports.DB_VERSION_TOKEN_PREFIX}${s}`;
45
+ }
46
+ /** Raw version embedded in a db:v1: token (for host SQL bind params). Absent → null. */
47
+ function dbTokenRaw(token) {
48
+ if (typeof token !== 'string' || !token.startsWith(exports.DB_VERSION_TOKEN_PREFIX))
49
+ return null;
50
+ if (token === exports.DB_ABSENT_TOKEN)
51
+ return null;
52
+ const raw = token.slice(exports.DB_VERSION_TOKEN_PREFIX.length);
53
+ return raw.length > 0 ? raw : null;
54
+ }
55
+ function normalizeDbReport(report) {
56
+ if ('rows_affected' in report && typeof report.rows_affected === 'number') {
57
+ const r = report;
58
+ if (r.rows_affected === 0) {
59
+ return {
60
+ kind: 'conflict',
61
+ new_version: undefined,
62
+ current_version: undefined,
63
+ rows_affected: 0,
64
+ };
65
+ }
66
+ return {
67
+ kind: 'committed',
68
+ new_version: r.new_version,
69
+ result: r.result,
70
+ rows_affected: r.rows_affected,
71
+ };
72
+ }
73
+ if ('status' in report && report.status === 'conflict') {
74
+ const r = report;
75
+ return {
76
+ kind: 'conflict',
77
+ new_version: undefined,
78
+ current_version: r.current_version,
79
+ };
80
+ }
81
+ if ('status' in report && report.status === 'committed') {
82
+ const r = report;
83
+ return {
84
+ kind: 'committed',
85
+ new_version: r.new_version,
86
+ result: r.result,
87
+ };
88
+ }
89
+ throw new Error('writeDbIfUnchanged: host write must return DbHostWriteReport');
90
+ }
91
+ /**
92
+ * Conditional DB write: re-read version; if token matches → host optimistic-lock write.
93
+ *
94
+ * Mapping (host report → ExecuteIfUnchangedOutcome):
95
+ * committed / rows_affected > 0 → committed
96
+ * conflict / rows_affected === 0 → refused / stale_version_token
97
+ * current_token from current_version when supplied,
98
+ * else null (no invented token)
99
+ *
100
+ * Wired through executeIfUnchanged — not a parallel path. Never composes SQL.
101
+ */
102
+ async function writeDbIfUnchanged(args) {
103
+ const detect = args.detect_stale_during_commit === true;
104
+ return (0, conditional_write_js_1.executeIfUnchanged)({
105
+ expected_token: args.expected_token,
106
+ current_token: async () => createDbVersionToken(await args.current_version()),
107
+ detect_stale_during_commit: detect,
108
+ expected_after_commit: detect
109
+ ? async (written) => {
110
+ if (typeof written.new_version === 'string' && written.new_version.trim().length > 0) {
111
+ return createDbVersionToken(written.new_version);
112
+ }
113
+ return createDbVersionToken(await args.current_version());
114
+ }
115
+ : undefined,
116
+ write: async () => {
117
+ const report = await args.write({
118
+ expected_version: dbTokenRaw(args.expected_token),
119
+ expected_token: args.expected_token,
120
+ });
121
+ const norm = normalizeDbReport(report);
122
+ if (norm.kind === 'conflict') {
123
+ const cur = norm.current_version !== undefined
124
+ ? createDbVersionToken(norm.current_version)
125
+ : null;
126
+ throw new conditional_write_js_1.StaleVersionTokenAbort(cur);
127
+ }
128
+ const new_version = norm.new_version === undefined || norm.new_version === null
129
+ ? null
130
+ : String(norm.new_version);
131
+ return {
132
+ new_version,
133
+ result: norm.result,
134
+ rows_affected: norm.rows_affected,
135
+ };
136
+ },
137
+ });
138
+ }
139
+ //# sourceMappingURL=db.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../../../src/cas-adapters/db.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Registry compareAndSwap CAS adapter — opaque registry/version token discipline over host-injected I/O.
3
+ *
4
+ * Uses executeIfUnchanged (conditional-write.ts). Does not invent a parallel outcome shape.
5
+ * Token format is opaque to the guard (string equality only).
6
+ *
7
+ * Token encoding (this adapter only; not interpreted by the core):
8
+ * registry:v1:<host_registry_token>
9
+ * Missing / empty token → REGISTRY_ABSENT_TOKEN.
10
+ *
11
+ * HONESTY: this adapter never talks to a registry client. The host callback performs
12
+ * compareAndSwap(expected, newValue) (or equivalent) and reports {swapped|conflict}.
13
+ * The adapter only normalizes tokens and maps the report onto ExecuteIfUnchangedOutcome.
14
+ *
15
+ * No registry SDKs — all I/O is host-injected.
16
+ */
17
+ import { tokensEqual, type ExecuteIfUnchangedOutcome, type VersionToken } from '../conditional-write.js';
18
+ /** Prefix for this adapter's opaque tokens (equality-only outside this module). */
19
+ export declare const REGISTRY_VERSION_TOKEN_PREFIX = "registry:v1:";
20
+ /**
21
+ * Token used when the registry key is absent / unversioned.
22
+ * Hosts that measured absence should pass this as expected_token for first put.
23
+ */
24
+ export declare const REGISTRY_ABSENT_TOKEN: VersionToken;
25
+ /**
26
+ * Build a VersionToken from a host registry revision / generation / etag-like value.
27
+ * null / undefined / empty → REGISTRY_ABSENT_TOKEN.
28
+ */
29
+ export declare function createRegistryVersionToken(token: string | number | bigint | null | undefined): VersionToken;
30
+ /** Raw value embedded in a registry:v1: token (for host CAS APIs). Absent → null. */
31
+ export declare function registryTokenRaw(token: VersionToken | null | undefined): string | null;
32
+ /**
33
+ * Host report after compareAndSwap (or equivalent).
34
+ */
35
+ export type RegistryHostCasReport<T = unknown> = {
36
+ swapped: true;
37
+ /** New registry token after successful swap; null/omit when unknown. */
38
+ new_token?: string | number | bigint | null;
39
+ result?: T;
40
+ } | {
41
+ swapped: false;
42
+ /** Current token if observed on conflict; omit → null (no invented token). */
43
+ current_token?: string | number | bigint | null;
44
+ } | {
45
+ status: 'committed';
46
+ new_token?: string | number | bigint | null;
47
+ result?: T;
48
+ } | {
49
+ status: 'conflict';
50
+ current_token?: string | number | bigint | null;
51
+ };
52
+ export type WriteRegistryIfUnchangedArgs<T = unknown> = {
53
+ /** Token measured before the host decided to write (from createRegistryVersionToken). */
54
+ expected_token: VersionToken;
55
+ /** Re-read the registry key's current token. Pre-write and post-write when detect is on. */
56
+ current_token: () => string | number | bigint | null | Promise<string | number | bigint | null>;
57
+ /**
58
+ * Host compareAndSwap. Receives the raw expected token for the CAS call.
59
+ * Adapter does not invoke any registry client.
60
+ */
61
+ compareAndSwap: (ctx: {
62
+ expected: string | null;
63
+ expected_token: VersionToken;
64
+ }) => RegistryHostCasReport<T> | Promise<RegistryHostCasReport<T>>;
65
+ /**
66
+ * Opt-in post-commit detection (default false).
67
+ * When host omits new_token on success, detection is a no-op.
68
+ */
69
+ detect_stale_during_commit?: boolean;
70
+ };
71
+ export type RegistryWriteResult<T = unknown> = {
72
+ new_token: string | null;
73
+ result: T | undefined;
74
+ };
75
+ /**
76
+ * Conditional registry write: re-read token; if matches → host compareAndSwap.
77
+ *
78
+ * Mapping (host report → ExecuteIfUnchangedOutcome):
79
+ * swapped:true / status:committed → committed
80
+ * swapped:false / status:conflict → refused / stale_version_token
81
+ * current_token from host when supplied, else null
82
+ *
83
+ * Wired through executeIfUnchanged — not a parallel path.
84
+ */
85
+ export declare function writeRegistryIfUnchanged<T = unknown>(args: WriteRegistryIfUnchangedArgs<T>): Promise<ExecuteIfUnchangedOutcome<RegistryWriteResult<T>>>;
86
+ /** Re-export equality helper for hosts comparing tokens without importing core module. */
87
+ export { tokensEqual };
88
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/cas-adapters/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,EAGL,WAAW,EACX,KAAK,yBAAyB,EAC9B,KAAK,YAAY,EAClB,MAAM,yBAAyB,CAAC;AAEjC,mFAAmF;AACnF,eAAO,MAAM,6BAA6B,iBAAiB,CAAC;AAE5D;;;GAGG;AACH,eAAO,MAAM,qBAAqB,EAAE,YAAmC,CAAC;AAExE;;;GAGG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,GACjD,YAAY,CAMd;AAED,qFAAqF;AACrF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAOtF;AAED;;GAEG;AACH,MAAM,MAAM,qBAAqB,CAAC,CAAC,GAAG,OAAO,IACzC;IACE,OAAO,EAAE,IAAI,CAAC;IACd,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC5C,MAAM,CAAC,EAAE,CAAC,CAAC;CACZ,GACD;IACE,OAAO,EAAE,KAAK,CAAC;IACf,8EAA8E;IAC9E,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;CACjD,GACD;IACE,MAAM,EAAE,WAAW,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC5C,MAAM,CAAC,EAAE,CAAC,CAAC;CACZ,GACD;IACE,MAAM,EAAE,UAAU,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;CACjD,CAAC;AAEN,MAAM,MAAM,4BAA4B,CAAC,CAAC,GAAG,OAAO,IAAI;IACtD,yFAAyF;IACzF,cAAc,EAAE,YAAY,CAAC;IAC7B,4FAA4F;IAC5F,aAAa,EAAE,MACX,MAAM,GACN,MAAM,GACN,MAAM,GACN,IAAI,GACJ,OAAO,CAAC,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7C;;;OAGG;IACH,cAAc,EAAE,CAAC,GAAG,EAAE;QACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QACxB,cAAc,EAAE,YAAY,CAAC;KAC9B,KAAK,qBAAqB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC,CAAC;IACnE;;;OAGG;IACH,0BAA0B,CAAC,EAAE,OAAO,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,mBAAmB,CAAC,CAAC,GAAG,OAAO,IAAI;IAC7C,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,EAAE,CAAC,GAAG,SAAS,CAAC;CACvB,CAAC;AA0BF;;;;;;;;;GASG;AACH,wBAAsB,wBAAwB,CAAC,CAAC,GAAG,OAAO,EACxD,IAAI,EAAE,4BAA4B,CAAC,CAAC,CAAC,GACpC,OAAO,CAAC,yBAAyB,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC,CAsC5D;AAED,0FAA0F;AAC1F,OAAO,EAAE,WAAW,EAAE,CAAC"}