@tokenoftrust/storefront-runner 2.4.6 → 2.4.7
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.
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
-- The candidate preparation ledger has no writer or reader: candidate progress lives in the KV
|
|
2
|
+
-- ReviewEnvironment index, and the release state in the tenant pipeline. Its tables are renamed
|
|
3
|
+
-- aside, not dropped, so the rows survive until a backup is taken; rename back to restore. The
|
|
4
|
+
-- indexes are dropped so their names are free (they are re-creatable from the rows).
|
|
5
|
+
DROP INDEX IF EXISTS `idx_candidate_attempts_tenant`;
|
|
6
|
+
DROP INDEX IF EXISTS `idx_candidate_attempts_candidate`;
|
|
7
|
+
DROP INDEX IF EXISTS `uq_candidate_attempts_candidate_number`;
|
|
8
|
+
ALTER TABLE `candidate_attempts` RENAME TO `retired_candidate_attempts_20261010`;
|
|
9
|
+
DROP INDEX IF EXISTS `idx_candidates_tenant_status`;
|
|
10
|
+
DROP INDEX IF EXISTS `uq_candidates_tenant_change`;
|
|
11
|
+
DROP INDEX IF EXISTS `uq_candidates_tenant_repo_pr`;
|
|
12
|
+
ALTER TABLE `candidates` RENAME TO `retired_candidates_20261010`;
|
|
@@ -564,94 +564,6 @@ export const newsletterSends = sqliteTable(
|
|
|
564
564
|
(t) => ({ pk: primaryKey({ columns: [t.tenantId, t.collection, t.digestId] }) }),
|
|
565
565
|
);
|
|
566
566
|
|
|
567
|
-
// --- Canonical publish-candidate identity -----------------------------------
|
|
568
|
-
// ONE canonical candidate per `(tenant_id, repository, pr_number)` — the real
|
|
569
|
-
// forge PR. Retries/rebuilds are NOT new candidates; they are child `attempts`
|
|
570
|
-
// (below). This is the durable identity spine other units resolve against; the
|
|
571
|
-
// KV `ReviewEnvironment` (`cust:*`) and aggregate/release models (`rel:*`) are
|
|
572
|
-
// read projections that reference this row's `change_id`/`pr_number`.
|
|
573
|
-
//
|
|
574
|
-
// `id` is a SYNTHETIC surrogate (`cand_<uuid>`), deliberately DISTINCT from the
|
|
575
|
-
// canonical key so foreign keys (attempts) never depend on the mutable natural
|
|
576
|
-
// key. `change_id` is the REAL forge change id — the stable branch/build
|
|
577
|
-
// identity (`candidate/<change_id>`) — preserved, never derived from `id`.
|
|
578
|
-
// `tenant_id` is the isolation boundary, written from the resolved session,
|
|
579
|
-
// NEVER a client body. The UNIQUE `(tenant_id, repository, pr_number)` is what
|
|
580
|
-
// makes "one candidate per PR" a schema invariant (upsert-by-key, not
|
|
581
|
-
// insert-then-dedupe); the UNIQUE `(tenant_id, change_id)` keeps the branch/
|
|
582
|
-
// build identity unambiguous for resolve-by-changeId callers.
|
|
583
|
-
export const candidates = sqliteTable(
|
|
584
|
-
"candidates",
|
|
585
|
-
{
|
|
586
|
-
id: text("id").primaryKey(),
|
|
587
|
-
tenantId: text("tenant_id").notNull(),
|
|
588
|
-
// The `<tenant>-<tag>` forge repository the PR lives in.
|
|
589
|
-
repository: text("repository").notNull(),
|
|
590
|
-
// The forge PR number — the third leg of the canonical key.
|
|
591
|
-
prNumber: integer("pr_number").notNull(),
|
|
592
|
-
// The real forge change id — stable branch/build identity (`candidate/<change_id>`).
|
|
593
|
-
changeId: text("change_id").notNull(),
|
|
594
|
-
// Source branch, conventionally `candidate/<change_id>` (nullable convenience mirror).
|
|
595
|
-
branch: text("branch"),
|
|
596
|
-
title: text("title"),
|
|
597
|
-
author: text("author"),
|
|
598
|
-
// Canonical candidate lifecycle: open | preparing | ready | needs-attention | accepted | rejected | closed.
|
|
599
|
-
status: text("status").notNull().default("open"),
|
|
600
|
-
// The PR head/base shas as last reconciled (nullable until first read).
|
|
601
|
-
headSha: text("head_sha"),
|
|
602
|
-
baseSha: text("base_sha"),
|
|
603
|
-
createdAt: text("created_at").notNull(),
|
|
604
|
-
updatedAt: text("updated_at").notNull(),
|
|
605
|
-
},
|
|
606
|
-
(t) => ({
|
|
607
|
-
uqCandidatesCanonical: uniqueIndex("uq_candidates_tenant_repo_pr").on(
|
|
608
|
-
t.tenantId,
|
|
609
|
-
t.repository,
|
|
610
|
-
t.prNumber,
|
|
611
|
-
),
|
|
612
|
-
uqCandidatesChange: uniqueIndex("uq_candidates_tenant_change").on(t.tenantId, t.changeId),
|
|
613
|
-
idxCandidatesTenantStatus: index("idx_candidates_tenant_status").on(t.tenantId, t.status),
|
|
614
|
-
}),
|
|
615
|
-
);
|
|
616
|
-
|
|
617
|
-
// One `attempt` = one build/check/rebase EXECUTION of a candidate. Attempts are
|
|
618
|
-
// the child rows retries produce — they must NEVER queue as separate top-level
|
|
619
|
-
// candidate rows. `candidate_id` references `candidates.id` in code only (no SQL
|
|
620
|
-
// FK — repo convention, matches `webhook_deliveries.event_id` / `orders`).
|
|
621
|
-
// `tenant_id` is denormalized for tenant-scoped reads/isolation without a join.
|
|
622
|
-
// `attempt_number` is monotonic per candidate (1,2,3…) giving stable ordering;
|
|
623
|
-
// UNIQUE `(candidate_id, attempt_number)` makes a double-assigned number a loud
|
|
624
|
-
// failure rather than a silent duplicate. `kind`: build | check | rebase;
|
|
625
|
-
// `status`: pending | running | passed | failed | cancelled (mirrors
|
|
626
|
-
// `IntegrationRun`'s state vocabulary). `evidence` is a JSON EvidenceReport
|
|
627
|
-
// snapshot; `detail` a human-legible terminal-state message.
|
|
628
|
-
export const candidateAttempts = sqliteTable(
|
|
629
|
-
"candidate_attempts",
|
|
630
|
-
{
|
|
631
|
-
id: text("id").primaryKey(),
|
|
632
|
-
candidateId: text("candidate_id").notNull(),
|
|
633
|
-
tenantId: text("tenant_id").notNull(),
|
|
634
|
-
attemptNumber: integer("attempt_number").notNull(),
|
|
635
|
-
kind: text("kind").notNull(),
|
|
636
|
-
status: text("status").notNull().default("pending"),
|
|
637
|
-
// The candidate head sha this attempt executed against, or null if unknown.
|
|
638
|
-
headSha: text("head_sha"),
|
|
639
|
-
detail: text("detail"),
|
|
640
|
-
evidence: text("evidence"),
|
|
641
|
-
startedAt: text("started_at").notNull(),
|
|
642
|
-
// Null while pending/running; set on a terminal transition.
|
|
643
|
-
finishedAt: text("finished_at"),
|
|
644
|
-
},
|
|
645
|
-
(t) => ({
|
|
646
|
-
uqAttemptNumber: uniqueIndex("uq_candidate_attempts_candidate_number").on(
|
|
647
|
-
t.candidateId,
|
|
648
|
-
t.attemptNumber,
|
|
649
|
-
),
|
|
650
|
-
idxAttemptsCandidate: index("idx_candidate_attempts_candidate").on(t.candidateId),
|
|
651
|
-
idxAttemptsTenant: index("idx_candidate_attempts_tenant").on(t.tenantId),
|
|
652
|
-
}),
|
|
653
|
-
);
|
|
654
|
-
|
|
655
567
|
// One active submit per tenant/change. This serializes identity authorization,
|
|
656
568
|
// the Gitea mutation, and audit finalization so a verified callback cannot
|
|
657
569
|
// become stale between its forge read and its KV writes. `expires_at` is
|
|
@@ -27,7 +27,6 @@ import type {
|
|
|
27
27
|
MembershipEntry,
|
|
28
28
|
MergeCandidate,
|
|
29
29
|
PreviewJobServices,
|
|
30
|
-
ReadModelTip,
|
|
31
30
|
} from "../../src/lib/pipeline/preview-jobs";
|
|
32
31
|
|
|
33
32
|
/** Files a fake build materializes. */
|
|
@@ -176,11 +175,6 @@ export function fakePreviewServices(state: DurableObjectState): PreviewJobServic
|
|
|
176
175
|
return row?.sha ?? null;
|
|
177
176
|
},
|
|
178
177
|
|
|
179
|
-
async readModelTip(): Promise<ReadModelTip | null> {
|
|
180
|
-
const rm = sql.exec<{ sha: string; membership: string; green: number }>(`SELECT * FROM fake_read_model`).toArray()[0];
|
|
181
|
-
return rm ? { sha: rm.sha, membership: JSON.parse(rm.membership), green: rm.green === 1 } : null;
|
|
182
|
-
},
|
|
183
|
-
|
|
184
178
|
async materialize({ sha, resume }) {
|
|
185
179
|
if (await fires("evict-before-build")) evict("before build");
|
|
186
180
|
const moveTo = sql
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/storefront-runner",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.7",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE",
|
|
5
5
|
"description": "World-shareable storefront runner: multi-tenant renderer on Astro/Cloudflare. No control plane.",
|
|
6
6
|
"packageManager": "pnpm@11.9.0",
|
|
@@ -147,8 +147,7 @@ export interface ReviewEnvironment {
|
|
|
147
147
|
* seeds this projection BEFORE the async reconcile webhook ever fires, so a
|
|
148
148
|
* dead/unregistered webhook shows as "queued since <submittedAt>, never
|
|
149
149
|
* picked up" instead of no record at all). Additive + optional: every OTHER
|
|
150
|
-
* writer of this projection (`reconcileCandidate`,
|
|
151
|
-
* terminal cleanup) simply carries it forward unset/
|
|
150
|
+
* writer of this projection (`reconcileCandidate`, terminal cleanup) simply carries it forward unset/
|
|
152
151
|
* untouched — only the submit-ack path ever sets it. Deliberately distinct
|
|
153
152
|
* from `updatedAt`, which every writer bumps on any recompute for any reason.
|
|
154
153
|
*/
|