@cleocode/core 2026.10.10-canary.2 → 2026.10.10

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 (140) hide show
  1. package/dist/build-provenance.json +2 -2
  2. package/dist/cloud/auto-sync-command.d.ts +28 -4
  3. package/dist/cloud/auto-sync-command.d.ts.map +1 -1
  4. package/dist/cloud/auto-sync-command.js +55 -10
  5. package/dist/cloud/auto-sync-command.js.map +1 -1
  6. package/dist/cloud/auto-sync.d.ts +12 -0
  7. package/dist/cloud/auto-sync.d.ts.map +1 -1
  8. package/dist/cloud/auto-sync.js +22 -0
  9. package/dist/cloud/auto-sync.js.map +1 -1
  10. package/dist/cloud/nexus-cloud-status.d.ts.map +1 -1
  11. package/dist/cloud/nexus-cloud-status.js +5 -0
  12. package/dist/cloud/nexus-cloud-status.js.map +1 -1
  13. package/dist/cloud/nexus-vault.d.ts.map +1 -1
  14. package/dist/cloud/nexus-vault.js +31 -3
  15. package/dist/cloud/nexus-vault.js.map +1 -1
  16. package/dist/cloud/secret-seal.d.ts +69 -0
  17. package/dist/cloud/secret-seal.d.ts.map +1 -0
  18. package/dist/cloud/secret-seal.js +148 -0
  19. package/dist/cloud/secret-seal.js.map +1 -0
  20. package/dist/docs/fetch-result.d.ts +7 -0
  21. package/dist/docs/fetch-result.d.ts.map +1 -1
  22. package/dist/docs/fetch-result.js +2 -1
  23. package/dist/docs/fetch-result.js.map +1 -1
  24. package/dist/docs/triage.d.ts.map +1 -1
  25. package/dist/docs/triage.js +13 -0
  26. package/dist/docs/triage.js.map +1 -1
  27. package/dist/memory/engine-compat.d.ts.map +1 -1
  28. package/dist/memory/engine-compat.js +14 -2
  29. package/dist/memory/engine-compat.js.map +1 -1
  30. package/dist/memory/graph-auto-populate.d.ts +22 -2
  31. package/dist/memory/graph-auto-populate.d.ts.map +1 -1
  32. package/dist/memory/graph-auto-populate.js +21 -22
  33. package/dist/memory/graph-auto-populate.js.map +1 -1
  34. package/dist/paths.d.ts +2 -1
  35. package/dist/paths.d.ts.map +1 -1
  36. package/dist/paths.js +12 -1
  37. package/dist/paths.js.map +1 -1
  38. package/dist/release/hooks-v1-promotion.d.ts +66 -0
  39. package/dist/release/hooks-v1-promotion.d.ts.map +1 -0
  40. package/dist/release/hooks-v1-promotion.js +115 -0
  41. package/dist/release/hooks-v1-promotion.js.map +1 -0
  42. package/dist/release/plan.d.ts +5 -0
  43. package/dist/release/plan.d.ts.map +1 -1
  44. package/dist/release/plan.js +26 -0
  45. package/dist/release/plan.js.map +1 -1
  46. package/dist/release/pr-evidence.d.ts +27 -0
  47. package/dist/release/pr-evidence.d.ts.map +1 -1
  48. package/dist/release/pr-evidence.js +75 -3
  49. package/dist/release/pr-evidence.js.map +1 -1
  50. package/dist/resources/admission-ledger.d.ts +34 -0
  51. package/dist/resources/admission-ledger.d.ts.map +1 -1
  52. package/dist/resources/admission-ledger.js +60 -7
  53. package/dist/resources/admission-ledger.js.map +1 -1
  54. package/dist/resources/run-governed.d.ts.map +1 -1
  55. package/dist/resources/run-governed.js +4 -2
  56. package/dist/resources/run-governed.js.map +1 -1
  57. package/dist/store/decision-adr-links.d.ts +152 -0
  58. package/dist/store/decision-adr-links.d.ts.map +1 -0
  59. package/dist/store/decision-adr-links.js +258 -0
  60. package/dist/store/decision-adr-links.js.map +1 -0
  61. package/dist/store/row-identity-registry.d.ts +4 -4
  62. package/dist/store/row-identity-registry.d.ts.map +1 -1
  63. package/dist/store/row-identity-registry.js +50 -8
  64. package/dist/store/row-identity-registry.js.map +1 -1
  65. package/dist/store/schema/cleo-global/accounts.d.ts +19 -1
  66. package/dist/store/schema/cleo-global/accounts.d.ts.map +1 -1
  67. package/dist/store/schema/cleo-global/accounts.js +7 -2
  68. package/dist/store/schema/cleo-global/accounts.js.map +1 -1
  69. package/dist/store/schema/cleo-global/services.d.ts +48 -0
  70. package/dist/store/schema/cleo-global/services.d.ts.map +1 -1
  71. package/dist/store/schema/cleo-global/services.js +10 -1
  72. package/dist/store/schema/cleo-global/services.js.map +1 -1
  73. package/dist/store/schema/cleo-project/docs.d.ts +162 -0
  74. package/dist/store/schema/cleo-project/docs.d.ts.map +1 -1
  75. package/dist/store/schema/cleo-project/docs.js +55 -1
  76. package/dist/store/schema/cleo-project/docs.js.map +1 -1
  77. package/dist/store/sync/apply/applier.d.ts +13 -0
  78. package/dist/store/sync/apply/applier.d.ts.map +1 -1
  79. package/dist/store/sync/apply/applier.js +43 -1
  80. package/dist/store/sync/apply/applier.js.map +1 -1
  81. package/dist/store/sync/apply/parent-delete.d.ts.map +1 -1
  82. package/dist/store/sync/apply/parent-delete.js +3 -0
  83. package/dist/store/sync/apply/parent-delete.js.map +1 -1
  84. package/dist/store/sync/merge/rules.d.ts +2 -1
  85. package/dist/store/sync/merge/rules.d.ts.map +1 -1
  86. package/dist/store/sync/merge/rules.js +18 -1
  87. package/dist/store/sync/merge/rules.js.map +1 -1
  88. package/dist/store/sync/pull.d.ts +6 -0
  89. package/dist/store/sync/pull.d.ts.map +1 -1
  90. package/dist/store/sync/pull.js +2 -0
  91. package/dist/store/sync/pull.js.map +1 -1
  92. package/dist/store/sync/push.d.ts +8 -0
  93. package/dist/store/sync/push.d.ts.map +1 -1
  94. package/dist/store/sync/push.js +5 -1
  95. package/dist/store/sync/push.js.map +1 -1
  96. package/dist/store/sync/sealer.d.ts +46 -0
  97. package/dist/store/sync/sealer.d.ts.map +1 -1
  98. package/dist/store/sync/sealer.js +123 -5
  99. package/dist/store/sync/sealer.js.map +1 -1
  100. package/dist/store/sync/timestamp-canon.d.ts.map +1 -1
  101. package/dist/store/sync/timestamp-canon.js +9 -1
  102. package/dist/store/sync/timestamp-canon.js.map +1 -1
  103. package/dist/store/table-classification.d.ts.map +1 -1
  104. package/dist/store/table-classification.js +6 -0
  105. package/dist/store/table-classification.js.map +1 -1
  106. package/dist/store/vault-manifest.d.ts.map +1 -1
  107. package/dist/store/vault-manifest.js +4 -0
  108. package/dist/store/vault-manifest.js.map +1 -1
  109. package/dist/store/with-retry.d.ts +3 -1
  110. package/dist/store/with-retry.d.ts.map +1 -1
  111. package/dist/store/with-retry.js +19 -5
  112. package/dist/store/with-retry.js.map +1 -1
  113. package/dist/tasks/affected-scope.d.ts +20 -0
  114. package/dist/tasks/affected-scope.d.ts.map +1 -1
  115. package/dist/tasks/affected-scope.js +37 -2
  116. package/dist/tasks/affected-scope.js.map +1 -1
  117. package/dist/tasks/complete.d.ts.map +1 -1
  118. package/dist/tasks/complete.js +37 -20
  119. package/dist/tasks/complete.js.map +1 -1
  120. package/dist/tasks/evidence.d.ts.map +1 -1
  121. package/dist/tasks/evidence.js +14 -0
  122. package/dist/tasks/evidence.js.map +1 -1
  123. package/dist/tasks/gate-runner.d.ts.map +1 -1
  124. package/dist/tasks/gate-runner.js +127 -32
  125. package/dist/tasks/gate-runner.js.map +1 -1
  126. package/dist/tasks/qa-run-binding.d.ts +35 -2
  127. package/dist/tasks/qa-run-binding.d.ts.map +1 -1
  128. package/dist/tasks/qa-run-binding.js +42 -19
  129. package/dist/tasks/qa-run-binding.js.map +1 -1
  130. package/dist/tasks/test-run-binding.d.ts +2 -1
  131. package/dist/tasks/test-run-binding.d.ts.map +1 -1
  132. package/dist/tasks/test-run-binding.js +25 -3
  133. package/dist/tasks/test-run-binding.js.map +1 -1
  134. package/dist/tasks/tool-semaphore.d.ts +7 -0
  135. package/dist/tasks/tool-semaphore.d.ts.map +1 -1
  136. package/dist/tasks/tool-semaphore.js +1 -1
  137. package/dist/tasks/tool-semaphore.js.map +1 -1
  138. package/migrations/drizzle-cleo-global/20261010190000_t13467-credential-uids/migration.sql +31 -0
  139. package/migrations/drizzle-cleo-project/20261012000000_t13359-decision-adr-links/migration.sql +63 -0
  140. package/package.json +13 -13
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Canonical accessors for `decision_adr_links` — the two-way link table
3
+ * between `D####` brain ledger decisions and curated ADR docs
4
+ * (T13359 · spec `cleodocs-rearch-design` §4/§10 · D11165).
5
+ *
6
+ * Every write goes through the functions here, on the `openDualScopeDb`
7
+ * chokepoint's drizzle handle ({@link getDb}) — gate 3 (no raw opens) and
8
+ * gate 28 (no raw writes to syncing tables).
9
+ *
10
+ * Semantics implemented here (the settled tiered methodology, spec §4):
11
+ *
12
+ * - The `D####` ledger stays the operational authority; ADR docs are the
13
+ * curated architecture record, authored when a decision graduates.
14
+ * Neither tier is generated from the other — this table is the only
15
+ * cross-reference, and it starts EMPTY (AC4: no ledger rewrite, no
16
+ * backfill of the ~360 existing decisions).
17
+ * - Identity is NATURAL over (decision_id, adr_slug): both devices derive
18
+ * the same uid for one pair, so concurrent identical links coalesce via
19
+ * `ON CONFLICT DO NOTHING` instead of breaking a unique index on apply
20
+ * (spec §10).
21
+ * - APPEND-ONLY: no DELETE exists. An unlink is a one-way monotonic latch
22
+ * flipping `status` to 'revoked' — the row itself is the tombstone and
23
+ * survives sync/restore. A revoked pair is never re-linked (rejection,
24
+ * not resurrection), so the latch can never be observed half-flipped.
25
+ * The latch is enforced at MERGE too (T13597): `SYNC_MERGE_RULES` marks
26
+ * 'revoked' absorbing with no leave op, so a concurrent same-uid link op
27
+ * from another device is refused by the merge engine and cannot
28
+ * resurrect the tombstone by LWW.
29
+ * - Neither column is a DB-level FK: `decision_id` is a cross-DB soft
30
+ * reference (the ledger lives in brain.db) and `adr_slug` is a mutable
31
+ * doc alias (old slugs resolve through `conduit_doc_slug_aliases`).
32
+ *
33
+ * @module
34
+ * @task T13359
35
+ * @epic T13340
36
+ * @saga T13339
37
+ */
38
+ import type { DecisionAdrLinkRow } from './schema/cleo-project/docs.js';
39
+ /** Link states. The revoke latch only ever moves `linked` → `revoked`. */
40
+ export declare const DECISION_ADR_LINK_STATUSES: readonly ["linked", "revoked"];
41
+ /** A live link state. */
42
+ export type DecisionAdrLinkStatus = (typeof DECISION_ADR_LINK_STATUSES)[number];
43
+ /**
44
+ * The natural row uid of one (decision, ADR) pair. Both devices derive this
45
+ * same value, which is what makes concurrent identical links coalesce.
46
+ *
47
+ * @param decisionId - The `D####` ledger decision id.
48
+ * @param adrSlug - The ADR doc slug.
49
+ * @returns The canonical natural uid.
50
+ */
51
+ export declare function decisionAdrLinkUid(decisionId: string, adrSlug: string): string;
52
+ /** Input for {@link linkDecisionToAdr}. */
53
+ export interface LinkDecisionToAdrInput {
54
+ /** The `D####` ledger decision id. */
55
+ readonly decisionId: string;
56
+ /** The ADR doc slug (`adr-NNN-…`). */
57
+ readonly adrSlug: string;
58
+ /** Agent id creating the link; omitted when unknown. */
59
+ readonly linkedBy?: string;
60
+ }
61
+ /** Outcome of a {@link linkDecisionToAdr} call. */
62
+ export interface LinkDecisionToAdrResult {
63
+ /** The link row after the call. */
64
+ readonly row: DecisionAdrLinkRow;
65
+ /** True when this call inserted the row; false for an idempotent re-link. */
66
+ readonly inserted: boolean;
67
+ }
68
+ /**
69
+ * Link a `D####` decision to an ADR doc (append-only, natural identity).
70
+ *
71
+ * A repeat link of an already-linked pair is an idempotent no-op. A link of
72
+ * a REVOKED pair is refused: append-only means the tombstone latch never
73
+ * flips back, so re-linking would require resurrecting a dead row.
74
+ *
75
+ * @param projectRoot - Absolute path to the CLEO project root.
76
+ * @param input - The link facts.
77
+ * @returns The link row and whether it was inserted.
78
+ */
79
+ export declare function linkDecisionToAdr(projectRoot: string, input: LinkDecisionToAdrInput): Promise<LinkDecisionToAdrResult>;
80
+ /** Input for {@link revokeDecisionAdrLink}. */
81
+ export interface RevokeDecisionAdrLinkInput {
82
+ /** The `D####` ledger decision id. */
83
+ readonly decisionId: string;
84
+ /** The ADR doc slug. */
85
+ readonly adrSlug: string;
86
+ /** Agent id revoking the link; omitted when unknown. */
87
+ readonly revokedBy?: string;
88
+ }
89
+ /** Outcome of a {@link revokeDecisionAdrLink} call. */
90
+ export interface RevokeDecisionAdrLinkResult {
91
+ /** The link row after the call. */
92
+ readonly row: DecisionAdrLinkRow;
93
+ /** True when this call flipped the latch; false for an idempotent re-revoke. */
94
+ readonly revoked: boolean;
95
+ }
96
+ /**
97
+ * Revoke a link as a TOMBSTONE LATCH (never a DELETE, never a resurrection).
98
+ *
99
+ * `status` flips `linked` → `revoked` exactly once and `updated_at` records
100
+ * the tombstone instant; the row survives sync and restore as the permanent
101
+ * record that this pair was once linked and then unlinked. Re-revoking an
102
+ * already-revoked pair is an idempotent no-op. Revoking a pair that was
103
+ * never linked is refused — a tombstone must reference a real link.
104
+ *
105
+ * @param projectRoot - Absolute path to the CLEO project root.
106
+ * @param input - The revoke facts.
107
+ * @returns The link row and whether the latch flipped.
108
+ */
109
+ export declare function revokeDecisionAdrLink(projectRoot: string, input: RevokeDecisionAdrLinkInput): Promise<RevokeDecisionAdrLinkResult>;
110
+ /** Read one link row by its natural key, any state. */
111
+ export declare function getDecisionAdrLink(projectRoot: string, decisionId: string, adrSlug: string): Promise<DecisionAdrLinkRow | null>;
112
+ /**
113
+ * The ADR docs linked to one decision — the decision→ADR direction of the
114
+ * two-way surfacing (spec §4: decision lookups surface their ADRs).
115
+ *
116
+ * @param projectRoot - Absolute path to the CLEO project root.
117
+ * @param decisionId - The `D####` ledger decision id.
118
+ * @param opts.includeRevoked - Also return tombstoned links (default false).
119
+ * @returns The link rows, ADR slug ascending.
120
+ */
121
+ export declare function listAdrLinksForDecision(projectRoot: string, decisionId: string, opts?: {
122
+ includeRevoked?: boolean;
123
+ }): Promise<DecisionAdrLinkRow[]>;
124
+ /**
125
+ * The `D####` decisions linked to one ADR doc — the ADR→decision direction
126
+ * (`cleo docs fetch adr-NNN` surfaces its source decisions, spec §4 AC2).
127
+ *
128
+ * @param projectRoot - Absolute path to the CLEO project root.
129
+ * @param adrSlug - The ADR doc slug.
130
+ * @param opts.includeRevoked - Also return tombstoned links (default false).
131
+ * @returns The link rows, decision id ascending.
132
+ */
133
+ export declare function listDecisionLinksForAdr(projectRoot: string, adrSlug: string, opts?: {
134
+ includeRevoked?: boolean;
135
+ }): Promise<DecisionAdrLinkRow[]>;
136
+ /** Optional `linkedAdrs` field attached by {@link attachLinkedAdrs}. */
137
+ export interface DecisionWithLinkedAdrs {
138
+ /** ADR doc slugs with a live link to this decision; absent when none. */
139
+ readonly linkedAdrs?: string[];
140
+ }
141
+ /**
142
+ * Attach each `D####` decision's linked ADR slugs — the decision→ADR
143
+ * direction of the two-way trace (spec §4: decision lookups surface their
144
+ * curated ADR records). Rows without a bare `D####` id pass through
145
+ * untouched.
146
+ *
147
+ * @param projectRoot - Absolute path to the CLEO project root.
148
+ * @param decisions - Decision rows as returned by the brain store.
149
+ * @returns The same rows, with `linkedAdrs` added where links exist.
150
+ */
151
+ export declare function attachLinkedAdrs<T extends object>(projectRoot: string, decisions: readonly T[]): Promise<Array<T & DecisionWithLinkedAdrs>>;
152
+ //# sourceMappingURL=decision-adr-links.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decision-adr-links.d.ts","sourceRoot":"","sources":["../../src/store/decision-adr-links.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAMH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAIxE,0EAA0E;AAC1E,eAAO,MAAM,0BAA0B,gCAAiC,CAAC;AAEzE,yBAAyB;AACzB,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,0BAA0B,CAAC,CAAC,MAAM,CAAC,CAAC;AAkBhF;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAE9E;AAED,2CAA2C;AAC3C,MAAM,WAAW,sBAAsB;IACrC,sCAAsC;IACtC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,sCAAsC;IACtC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,wDAAwD;IACxD,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,mDAAmD;AACnD,MAAM,WAAW,uBAAuB;IACtC,mCAAmC;IACnC,QAAQ,CAAC,GAAG,EAAE,kBAAkB,CAAC;IACjC,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED;;;;;;;;;;GAUG;AAEH,wBAAsB,iBAAiB,CACrC,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,sBAAsB,GAC5B,OAAO,CAAC,uBAAuB,CAAC,CAgElC;AAED,+CAA+C;AAC/C,MAAM,WAAW,0BAA0B;IACzC,sCAAsC;IACtC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,wBAAwB;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,wDAAwD;IACxD,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,uDAAuD;AACvD,MAAM,WAAW,2BAA2B;IAC1C,mCAAmC;IACnC,QAAQ,CAAC,GAAG,EAAE,kBAAkB,CAAC;IACjC,gFAAgF;IAChF,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED;;;;;;;;;;;;GAYG;AAEH,wBAAsB,qBAAqB,CACzC,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,0BAA0B,GAChC,OAAO,CAAC,2BAA2B,CAAC,CAwDtC;AAED,uDAAuD;AACvD,wBAAsB,kBAAkB,CACtC,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAUpC;AAED;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CAC3C,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,IAAI,CAAC,EAAE;IAAE,cAAc,CAAC,EAAE,OAAO,CAAA;CAAE,GAClC,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAU/B;AAED;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CAC3C,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE;IAAE,cAAc,CAAC,EAAE,OAAO,CAAA;CAAE,GAClC,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAU/B;AAED,wEAAwE;AACxE,MAAM,WAAW,sBAAsB;IACrC,yEAAyE;IACzE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CAChC;AAED;;;;;;;;;GASG;AACH,wBAAsB,gBAAgB,CAAC,CAAC,SAAS,MAAM,EACrD,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,SAAS,CAAC,EAAE,GACtB,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,sBAAsB,CAAC,CAAC,CAgB5C"}
@@ -0,0 +1,258 @@
1
+ /**
2
+ * Canonical accessors for `decision_adr_links` — the two-way link table
3
+ * between `D####` brain ledger decisions and curated ADR docs
4
+ * (T13359 · spec `cleodocs-rearch-design` §4/§10 · D11165).
5
+ *
6
+ * Every write goes through the functions here, on the `openDualScopeDb`
7
+ * chokepoint's drizzle handle ({@link getDb}) — gate 3 (no raw opens) and
8
+ * gate 28 (no raw writes to syncing tables).
9
+ *
10
+ * Semantics implemented here (the settled tiered methodology, spec §4):
11
+ *
12
+ * - The `D####` ledger stays the operational authority; ADR docs are the
13
+ * curated architecture record, authored when a decision graduates.
14
+ * Neither tier is generated from the other — this table is the only
15
+ * cross-reference, and it starts EMPTY (AC4: no ledger rewrite, no
16
+ * backfill of the ~360 existing decisions).
17
+ * - Identity is NATURAL over (decision_id, adr_slug): both devices derive
18
+ * the same uid for one pair, so concurrent identical links coalesce via
19
+ * `ON CONFLICT DO NOTHING` instead of breaking a unique index on apply
20
+ * (spec §10).
21
+ * - APPEND-ONLY: no DELETE exists. An unlink is a one-way monotonic latch
22
+ * flipping `status` to 'revoked' — the row itself is the tombstone and
23
+ * survives sync/restore. A revoked pair is never re-linked (rejection,
24
+ * not resurrection), so the latch can never be observed half-flipped.
25
+ * The latch is enforced at MERGE too (T13597): `SYNC_MERGE_RULES` marks
26
+ * 'revoked' absorbing with no leave op, so a concurrent same-uid link op
27
+ * from another device is refused by the merge engine and cannot
28
+ * resurrect the tombstone by LWW.
29
+ * - Neither column is a DB-level FK: `decision_id` is a cross-DB soft
30
+ * reference (the ledger lives in brain.db) and `adr_slug` is a mutable
31
+ * doc alias (old slugs resolve through `conduit_doc_slug_aliases`).
32
+ *
33
+ * @module
34
+ * @task T13359
35
+ * @epic T13340
36
+ * @saga T13339
37
+ */
38
+ import { ExitCode } from '@cleocode/contracts/exit-codes.js';
39
+ import { and, eq } from 'drizzle-orm';
40
+ import { CleoError } from '../errors.js';
41
+ import { naturalRowUid } from './row-identity.js';
42
+ import { decisionAdrLinks } from './schema/cleo-project/docs.js';
43
+ import { getDb } from './sqlite.js';
44
+ /** Link states. The revoke latch only ever moves `linked` → `revoked`. */
45
+ export const DECISION_ADR_LINK_STATUSES = ['linked', 'revoked'];
46
+ /** Bare `D####` ledger decision id shape (real brain ids, per T13358). */
47
+ const DECISION_ID_PATTERN = /^D\d+$/;
48
+ /**
49
+ * Refuse an invalid input at the write boundary.
50
+ *
51
+ * @param name - Field name for the message.
52
+ * @param value - The value to check.
53
+ */
54
+ function requireNonEmpty(name, value) {
55
+ if (value.trim().length === 0) {
56
+ // @sync-invariant none:input-shape a blank identifier is refused before any write; nothing was written
57
+ throw new CleoError(ExitCode.VALIDATION_ERROR, `${name} must be a non-empty string`);
58
+ }
59
+ }
60
+ /**
61
+ * The natural row uid of one (decision, ADR) pair. Both devices derive this
62
+ * same value, which is what makes concurrent identical links coalesce.
63
+ *
64
+ * @param decisionId - The `D####` ledger decision id.
65
+ * @param adrSlug - The ADR doc slug.
66
+ * @returns The canonical natural uid.
67
+ */
68
+ export function decisionAdrLinkUid(decisionId, adrSlug) {
69
+ return naturalRowUid('project', 'decision_adr_links', [decisionId, adrSlug]);
70
+ }
71
+ /**
72
+ * Link a `D####` decision to an ADR doc (append-only, natural identity).
73
+ *
74
+ * A repeat link of an already-linked pair is an idempotent no-op. A link of
75
+ * a REVOKED pair is refused: append-only means the tombstone latch never
76
+ * flips back, so re-linking would require resurrecting a dead row.
77
+ *
78
+ * @param projectRoot - Absolute path to the CLEO project root.
79
+ * @param input - The link facts.
80
+ * @returns The link row and whether it was inserted.
81
+ */
82
+ // @sync-invariant docs.decision-adr-links.revoke-monotonic the read of the existing row and the conditional insert are one causal step: the read refuses a tombstoned pair (no resurrection) and the natural uid + ON CONFLICT DO NOTHING makes a concurrent identical link coalesce into the same row
83
+ export async function linkDecisionToAdr(projectRoot, input) {
84
+ requireNonEmpty('decisionId', input.decisionId);
85
+ requireNonEmpty('adrSlug', input.adrSlug);
86
+ if (!DECISION_ID_PATTERN.test(input.decisionId)) {
87
+ // @sync-invariant none:input-shape only bare D#### ledger ids may anchor a link; nothing was written
88
+ throw new CleoError(ExitCode.VALIDATION_ERROR, `decisionId must be a bare D#### ledger id (got '${input.decisionId}')`);
89
+ }
90
+ const db = await getDb(projectRoot);
91
+ const existing = await db
92
+ .select()
93
+ .from(decisionAdrLinks)
94
+ .where(and(eq(decisionAdrLinks.decisionId, input.decisionId), eq(decisionAdrLinks.adrSlug, input.adrSlug)))
95
+ .get();
96
+ if (existing) {
97
+ if (existing.status === 'revoked') {
98
+ // @sync-invariant docs.decision-adr-links.revoke-monotonic the revoke latch is one-way; re-linking a tombstoned pair would resurrect a dead row
99
+ throw new CleoError(ExitCode.VALIDATION_ERROR, `link ${input.decisionId} → ${input.adrSlug} was revoked; a revoked pair is never re-linked`);
100
+ }
101
+ return { row: existing, inserted: false };
102
+ }
103
+ const uid = decisionAdrLinkUid(input.decisionId, input.adrSlug);
104
+ const now = new Date().toISOString();
105
+ await db
106
+ .insert(decisionAdrLinks)
107
+ .values({
108
+ decisionId: input.decisionId,
109
+ adrSlug: input.adrSlug,
110
+ status: 'linked',
111
+ ...(input.linkedBy !== undefined ? { linkedBy: input.linkedBy } : {}),
112
+ createdAt: now,
113
+ updatedAt: now,
114
+ uid,
115
+ })
116
+ .onConflictDoNothing()
117
+ .run();
118
+ const row = await db
119
+ .select()
120
+ .from(decisionAdrLinks)
121
+ .where(and(eq(decisionAdrLinks.decisionId, input.decisionId), eq(decisionAdrLinks.adrSlug, input.adrSlug)))
122
+ .get();
123
+ if (!row) {
124
+ // @sync-invariant docs.decision-adr-links.revoke-monotonic a link insert that cannot be read back means the store lost the write; fail loudly instead of returning a phantom
125
+ throw new CleoError(ExitCode.GENERAL_ERROR, `link insert for ${input.decisionId} → ${input.adrSlug} could not be read back`);
126
+ }
127
+ return { row, inserted: true };
128
+ }
129
+ /**
130
+ * Revoke a link as a TOMBSTONE LATCH (never a DELETE, never a resurrection).
131
+ *
132
+ * `status` flips `linked` → `revoked` exactly once and `updated_at` records
133
+ * the tombstone instant; the row survives sync and restore as the permanent
134
+ * record that this pair was once linked and then unlinked. Re-revoking an
135
+ * already-revoked pair is an idempotent no-op. Revoking a pair that was
136
+ * never linked is refused — a tombstone must reference a real link.
137
+ *
138
+ * @param projectRoot - Absolute path to the CLEO project root.
139
+ * @param input - The revoke facts.
140
+ * @returns The link row and whether the latch flipped.
141
+ */
142
+ // @sync-invariant docs.decision-adr-links.revoke-monotonic the read-modify-write IS the one-way latch: flip linked→revoked exactly once, refuse if never linked; no DELETE exists on this table
143
+ export async function revokeDecisionAdrLink(projectRoot, input) {
144
+ requireNonEmpty('decisionId', input.decisionId);
145
+ requireNonEmpty('adrSlug', input.adrSlug);
146
+ const db = await getDb(projectRoot);
147
+ const existing = await db
148
+ .select()
149
+ .from(decisionAdrLinks)
150
+ .where(and(eq(decisionAdrLinks.decisionId, input.decisionId), eq(decisionAdrLinks.adrSlug, input.adrSlug)))
151
+ .get();
152
+ if (!existing) {
153
+ // @sync-invariant docs.decision-adr-links.revoke-monotonic a tombstone must reference a real link; revoking a never-linked pair would fabricate history
154
+ throw new CleoError(ExitCode.NOT_FOUND, `link ${input.decisionId} → ${input.adrSlug} does not exist; nothing to revoke`);
155
+ }
156
+ if (existing.status === 'revoked') {
157
+ return { row: existing, revoked: false };
158
+ }
159
+ await db
160
+ .update(decisionAdrLinks)
161
+ .set({
162
+ status: 'revoked',
163
+ ...(input.revokedBy !== undefined ? { revokedBy: input.revokedBy } : {}),
164
+ updatedAt: new Date().toISOString(),
165
+ })
166
+ .where(and(eq(decisionAdrLinks.decisionId, input.decisionId), eq(decisionAdrLinks.adrSlug, input.adrSlug)))
167
+ .run();
168
+ const row = await db
169
+ .select()
170
+ .from(decisionAdrLinks)
171
+ .where(and(eq(decisionAdrLinks.decisionId, input.decisionId), eq(decisionAdrLinks.adrSlug, input.adrSlug)))
172
+ .get();
173
+ if (!row) {
174
+ // @sync-invariant docs.decision-adr-links.revoke-monotonic a revoked row must still exist — the tombstone is a surviving row, never a disappearance
175
+ throw new CleoError(ExitCode.GENERAL_ERROR, `link ${input.decisionId} → ${input.adrSlug} vanished during its revoke tombstone`);
176
+ }
177
+ return { row, revoked: true };
178
+ }
179
+ /** Read one link row by its natural key, any state. */
180
+ export async function getDecisionAdrLink(projectRoot, decisionId, adrSlug) {
181
+ requireNonEmpty('decisionId', decisionId);
182
+ requireNonEmpty('adrSlug', adrSlug);
183
+ const db = await getDb(projectRoot);
184
+ const row = await db
185
+ .select()
186
+ .from(decisionAdrLinks)
187
+ .where(and(eq(decisionAdrLinks.decisionId, decisionId), eq(decisionAdrLinks.adrSlug, adrSlug)))
188
+ .get();
189
+ return row ?? null;
190
+ }
191
+ /**
192
+ * The ADR docs linked to one decision — the decision→ADR direction of the
193
+ * two-way surfacing (spec §4: decision lookups surface their ADRs).
194
+ *
195
+ * @param projectRoot - Absolute path to the CLEO project root.
196
+ * @param decisionId - The `D####` ledger decision id.
197
+ * @param opts.includeRevoked - Also return tombstoned links (default false).
198
+ * @returns The link rows, ADR slug ascending.
199
+ */
200
+ export async function listAdrLinksForDecision(projectRoot, decisionId, opts) {
201
+ requireNonEmpty('decisionId', decisionId);
202
+ const db = await getDb(projectRoot);
203
+ const rows = await db
204
+ .select()
205
+ .from(decisionAdrLinks)
206
+ .where(eq(decisionAdrLinks.decisionId, decisionId))
207
+ .all();
208
+ const filtered = opts?.includeRevoked === true ? rows : rows.filter((r) => r.status === 'linked');
209
+ return filtered.sort((a, b) => a.adrSlug.localeCompare(b.adrSlug));
210
+ }
211
+ /**
212
+ * The `D####` decisions linked to one ADR doc — the ADR→decision direction
213
+ * (`cleo docs fetch adr-NNN` surfaces its source decisions, spec §4 AC2).
214
+ *
215
+ * @param projectRoot - Absolute path to the CLEO project root.
216
+ * @param adrSlug - The ADR doc slug.
217
+ * @param opts.includeRevoked - Also return tombstoned links (default false).
218
+ * @returns The link rows, decision id ascending.
219
+ */
220
+ export async function listDecisionLinksForAdr(projectRoot, adrSlug, opts) {
221
+ requireNonEmpty('adrSlug', adrSlug);
222
+ const db = await getDb(projectRoot);
223
+ const rows = await db
224
+ .select()
225
+ .from(decisionAdrLinks)
226
+ .where(eq(decisionAdrLinks.adrSlug, adrSlug))
227
+ .all();
228
+ const filtered = opts?.includeRevoked === true ? rows : rows.filter((r) => r.status === 'linked');
229
+ return filtered.sort((a, b) => a.decisionId.localeCompare(b.decisionId));
230
+ }
231
+ /**
232
+ * Attach each `D####` decision's linked ADR slugs — the decision→ADR
233
+ * direction of the two-way trace (spec §4: decision lookups surface their
234
+ * curated ADR records). Rows without a bare `D####` id pass through
235
+ * untouched.
236
+ *
237
+ * @param projectRoot - Absolute path to the CLEO project root.
238
+ * @param decisions - Decision rows as returned by the brain store.
239
+ * @returns The same rows, with `linkedAdrs` added where links exist.
240
+ */
241
+ export async function attachLinkedAdrs(projectRoot, decisions) {
242
+ const out = [];
243
+ for (const row of decisions) {
244
+ const id = 'id' in row ? row.id : undefined;
245
+ if (typeof id !== 'string' || !DECISION_ID_PATTERN.test(id)) {
246
+ out.push(row);
247
+ continue;
248
+ }
249
+ const links = await listAdrLinksForDecision(projectRoot, id);
250
+ if (links.length === 0) {
251
+ out.push(row);
252
+ continue;
253
+ }
254
+ out.push({ ...row, linkedAdrs: links.map((link) => link.adrSlug) });
255
+ }
256
+ return out;
257
+ }
258
+ //# sourceMappingURL=decision-adr-links.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decision-adr-links.js","sourceRoot":"","sources":["../../src/store/decision-adr-links.ts"],"names":[],"mappings":"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"}
@@ -81,9 +81,10 @@ export declare const ROW_IDENTITY_EXEMPT: Readonly<Record<TableScope, Readonly<R
81
81
  * Since T12894 declared the brain text-keyed tables (11 project, 12 global),
82
82
  * T12895 the brain natural-key tables (3 project, 3 global) and T12896 the
83
83
  * brain integer-keyed tables (6 project, 6 global), T12915 declared the
84
- * agent registry (1 global; 6 more reclassified derived or local-only), and
84
+ * agent registry (1 global; 6 more reclassified derived or local-only),
85
85
  * T13354 declared the re-keyed conduit doc family (5 project, dropping 4
86
- * exemptions — contributors became derived and not syncing)
86
+ * exemptions — contributors became derived and not syncing), and T13467 the
87
+ * credential tables (4 global)
87
88
  * ({@link rowIdentityExemptionSummary} prints the live numbers):
88
89
  *
89
90
  * | category | task | project | global |
@@ -93,14 +94,13 @@ export declare const ROW_IDENTITY_EXEMPT: Readonly<Record<TableScope, Readonly<R
93
94
  * | planned | T12896 | 2 | 1 |
94
95
  * | planned (conduit) | T12913 | 8 | 0 |
95
96
  * | planned (lifecycle…) | T12914 | 16 | 0 |
96
- * | planned (agents…) | T12915 | 0 | 4 |
97
97
  * | planned (nexus) | T12916 | 0 | 8 |
98
98
  * | planned (skills) | T12917 | 0 | 4 |
99
99
  * | planned (docs) | T12919 | 3 | 0 |
100
100
  * | planned (misc) | T12920 | 12 | 0 |
101
101
  * | twin-collapse | T12535 | 30 | 0 |
102
102
  * | not-row-replicated | T12918 | 1 | 1 |
103
- * | total | | 70 | 17 |
103
+ * | total | | 70 | 13 |
104
104
  *
105
105
  * @task T12897
106
106
  */
@@ -1 +1 @@
1
- {"version":3,"file":"row-identity-registry.d.ts","sourceRoot":"","sources":["../../src/store/row-identity-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAkB,eAAe,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEvF,yCAAyC;AACzC,eAAO,MAAM,UAAU,QAAQ,CAAC;AAEhC,2EAA2E;AAC3E,eAAO,MAAM,eAAe,aAAa,CAAC;AA6P1C;;;;;;;;;;GAUG;AACH,eAAO,MAAM,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,SAAS,eAAe,EAAE,CAAC,CAmNjF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,EAAE,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC,CAS/E,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,4BAA4B,GAAG,SAAS,GAAG,eAAe,GAAG,oBAAoB,CAAC;AAE9F;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC,sBAAsB;IACtB,QAAQ,CAAC,QAAQ,EAAE,4BAA4B,CAAC;IAChD,6EAA6E;IAC7E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,mEAAmE;IACnE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAsDD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mBAAmB,EAAE,QAAQ,CACxC,MAAM,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAC,CA4HnE,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,0BAA0B,EAAE,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAG3E,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,gCAAgC,EAAE,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAGjF,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,MAAM,CAIvE;AAED;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,UAAU,GAAG;IAC9D,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC,CAWA;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;IACvE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE;IAC9C,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACnC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAC;IAChE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;CACzC,GAAG,0BAA0B,EAAE,CAyD/B;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAE7F;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAQ7E"}
1
+ {"version":3,"file":"row-identity-registry.d.ts","sourceRoot":"","sources":["../../src/store/row-identity-registry.ts"],"names":[],"mappings":"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"}
@@ -450,6 +450,15 @@ export const ROW_IDENTITY = {
450
450
  key: ['attachment_uid'],
451
451
  task: 'T13354',
452
452
  },
453
+ {
454
+ // Natural over the pair (spec §10): both devices derive one uid for one
455
+ // link, so concurrent identical links coalesce on apply. The revoke is
456
+ // a one-way tombstone latch on the same row, never a second row.
457
+ table: 'decision_adr_links',
458
+ kind: 'natural',
459
+ key: ['decision_id', 'adr_slug'],
460
+ task: 'T13359',
461
+ },
453
462
  ],
454
463
  global: [
455
464
  ...brainTextKeyed(),
@@ -482,6 +491,41 @@ export const ROW_IDENTITY = {
482
491
  localKey: 'id',
483
492
  task: 'T12915',
484
493
  },
494
+ // T13467 (uid spec D4 amended 2026-10-11): the credential tables are natural
495
+ // on their non-secret identifiers, so the same credential held on two
496
+ // devices is one row. Their INTEGER `id` is a local key; every other column
497
+ // is sealed on the home stream (§2.7), never sent in the clear.
498
+ {
499
+ table: 'accounts',
500
+ kind: 'natural',
501
+ key: ['provider', 'label'],
502
+ localKey: 'id',
503
+ task: 'T13467',
504
+ },
505
+ {
506
+ table: 'service_connections',
507
+ kind: 'natural',
508
+ key: ['provider', 'label'],
509
+ localKey: 'id',
510
+ task: 'T13467',
511
+ },
512
+ {
513
+ table: 'service_configs',
514
+ kind: 'natural',
515
+ key: ['provider'],
516
+ localKey: 'id',
517
+ task: 'T13467',
518
+ },
519
+ {
520
+ table: 'agent_service_grants',
521
+ kind: 'natural',
522
+ key: ['agent_id', 'service_connection_id'],
523
+ keyRefs: [
524
+ { column: 'agent_id', table: 'agent_registry_agents' },
525
+ { column: 'service_connection_id', table: 'service_connections' },
526
+ ],
527
+ task: 'T13467',
528
+ },
485
529
  ],
486
530
  };
487
531
  /**
@@ -523,7 +567,6 @@ function cluster(task) {
523
567
  }
524
568
  const CONDUIT = cluster('T12913');
525
569
  const LIFECYCLE_RELEASE_GIT = cluster('T12914');
526
- const AGENT_ACCOUNTS_SERVICE = cluster('T12915');
527
570
  const NEXUS_REGISTRY = cluster('T12916');
528
571
  const SKILLS = cluster('T12917');
529
572
  const DOCS = cluster('T12919');
@@ -638,7 +681,6 @@ export const ROW_IDENTITY_EXEMPT = {
638
681
  },
639
682
  global: {
640
683
  ...BRAIN_EXEMPT,
641
- ...exempt(['accounts', 'agent_service_grants', 'service_configs', 'service_connections'], AGENT_ACCOUNTS_SERVICE),
642
684
  ...exempt([
643
685
  'nexus_audit_log',
644
686
  'nexus_devices',
@@ -661,9 +703,10 @@ export const ROW_IDENTITY_EXEMPT = {
661
703
  * Since T12894 declared the brain text-keyed tables (11 project, 12 global),
662
704
  * T12895 the brain natural-key tables (3 project, 3 global) and T12896 the
663
705
  * brain integer-keyed tables (6 project, 6 global), T12915 declared the
664
- * agent registry (1 global; 6 more reclassified derived or local-only), and
706
+ * agent registry (1 global; 6 more reclassified derived or local-only),
665
707
  * T13354 declared the re-keyed conduit doc family (5 project, dropping 4
666
- * exemptions — contributors became derived and not syncing)
708
+ * exemptions — contributors became derived and not syncing), and T13467 the
709
+ * credential tables (4 global)
667
710
  * ({@link rowIdentityExemptionSummary} prints the live numbers):
668
711
  *
669
712
  * | category | task | project | global |
@@ -673,20 +716,19 @@ export const ROW_IDENTITY_EXEMPT = {
673
716
  * | planned | T12896 | 2 | 1 |
674
717
  * | planned (conduit) | T12913 | 8 | 0 |
675
718
  * | planned (lifecycle…) | T12914 | 16 | 0 |
676
- * | planned (agents…) | T12915 | 0 | 4 |
677
719
  * | planned (nexus) | T12916 | 0 | 8 |
678
720
  * | planned (skills) | T12917 | 0 | 4 |
679
721
  * | planned (docs) | T12919 | 3 | 0 |
680
722
  * | planned (misc) | T12920 | 12 | 0 |
681
723
  * | twin-collapse | T12535 | 30 | 0 |
682
724
  * | not-row-replicated | T12918 | 1 | 1 |
683
- * | total | | 70 | 17 |
725
+ * | total | | 70 | 13 |
684
726
  *
685
727
  * @task T12897
686
728
  */
687
729
  export const ROW_IDENTITY_EXEMPT_PINNED = {
688
730
  project: 70,
689
- global: 17,
731
+ global: 13,
690
732
  };
691
733
  /**
692
734
  * sha256 of each scope's sorted exempt table names
@@ -698,7 +740,7 @@ export const ROW_IDENTITY_EXEMPT_PINNED = {
698
740
  */
699
741
  export const ROW_IDENTITY_EXEMPT_NAMES_SHA256 = {
700
742
  project: '03413c00c1a3a2be35c1b1c8729ceb8c89f28624e7919e6e12e2f06b5fba5ef6',
701
- global: '60877c1f83b6b1e87bbce6f6e911720853337aa2b10eed7404c06a47e9bffd5c',
743
+ global: '615415a2502708e84007335451603d186c3051cb7e8a69c0b48f561e485edd84',
702
744
  };
703
745
  /**
704
746
  * sha256 (hex) of a set of table names, sorted and newline-joined.