feat(schema)(#25): Request.keyUserId + SigningCondition lifecycle for live grant eval
Additive, non-breaking schema prep for the Option D live-evaluation ACL: - Request gains keyUserId (FK) + @@index([keyUserId, method]) so token usage caps can be derived live by COUNTing allowed Requests, replacing the never-enforced mutable PolicyRule.currentUsageCount (derive-don't-count, per lnbits/nostr_bunker prior art). - SigningCondition gains createdAt/expiresAt/revokedAt so the manual-override layer carries its own lifecycle and runs through the same grantIsLive(now) predicate as token grants (D1: two typed sources, one shared rule). No behavior change yet; the ACL hot path and applyToken de-materialization follow in subsequent commits. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a707d203a1
commit
6397c7988d
2 changed files with 58 additions and 2 deletions
|
|
@ -0,0 +1,37 @@
|
||||||
|
-- RedefineTables
|
||||||
|
PRAGMA defer_foreign_keys=ON;
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_Request" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"keyName" TEXT,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"requestId" TEXT NOT NULL,
|
||||||
|
"remotePubkey" TEXT NOT NULL,
|
||||||
|
"method" TEXT NOT NULL,
|
||||||
|
"params" TEXT,
|
||||||
|
"allowed" BOOLEAN,
|
||||||
|
"keyUserId" INTEGER,
|
||||||
|
CONSTRAINT "Request_keyUserId_fkey" FOREIGN KEY ("keyUserId") REFERENCES "KeyUser" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_Request" ("allowed", "createdAt", "id", "keyName", "method", "params", "remotePubkey", "requestId") SELECT "allowed", "createdAt", "id", "keyName", "method", "params", "remotePubkey", "requestId" FROM "Request";
|
||||||
|
DROP TABLE "Request";
|
||||||
|
ALTER TABLE "new_Request" RENAME TO "Request";
|
||||||
|
CREATE INDEX "Request_keyUserId_method_idx" ON "Request"("keyUserId", "method");
|
||||||
|
CREATE TABLE "new_SigningCondition" (
|
||||||
|
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
"method" TEXT,
|
||||||
|
"kind" TEXT,
|
||||||
|
"content" TEXT,
|
||||||
|
"keyUserKeyName" TEXT,
|
||||||
|
"allowed" BOOLEAN,
|
||||||
|
"keyUserId" INTEGER,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"expiresAt" DATETIME,
|
||||||
|
"revokedAt" DATETIME,
|
||||||
|
CONSTRAINT "SigningCondition_keyUserId_fkey" FOREIGN KEY ("keyUserId") REFERENCES "KeyUser" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_SigningCondition" ("allowed", "content", "id", "keyUserId", "keyUserKeyName", "kind", "method") SELECT "allowed", "content", "id", "keyUserId", "keyUserKeyName", "kind", "method" FROM "SigningCondition";
|
||||||
|
DROP TABLE "SigningCondition";
|
||||||
|
ALTER TABLE "new_SigningCondition" RENAME TO "SigningCondition";
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
|
PRAGMA defer_foreign_keys=OFF;
|
||||||
|
|
@ -17,6 +17,14 @@ model Request {
|
||||||
method String
|
method String
|
||||||
params String?
|
params String?
|
||||||
allowed Boolean?
|
allowed Boolean?
|
||||||
|
// Bind each request to the KeyUser it was evaluated against so usage
|
||||||
|
// caps can be derived live by COUNTing allowed Requests, instead of
|
||||||
|
// maintaining a mutable PolicyRule.currentUsageCount that drifts.
|
||||||
|
// See aiolabs/nsecbunkerd#25 (Option D, derive-don't-count).
|
||||||
|
keyUserId Int?
|
||||||
|
KeyUser KeyUser? @relation(fields: [keyUserId], references: [id])
|
||||||
|
|
||||||
|
@@index([keyUserId, method])
|
||||||
}
|
}
|
||||||
|
|
||||||
model KeyUser {
|
model KeyUser {
|
||||||
|
|
@ -31,6 +39,7 @@ model KeyUser {
|
||||||
logs Log[]
|
logs Log[]
|
||||||
signingConditions SigningCondition[]
|
signingConditions SigningCondition[]
|
||||||
Token Token[]
|
Token Token[]
|
||||||
|
requests Request[]
|
||||||
|
|
||||||
@@unique([keyName, userPubkey], name: "unique_key_user")
|
@@unique([keyName, userPubkey], name: "unique_key_user")
|
||||||
}
|
}
|
||||||
|
|
@ -56,15 +65,25 @@ model User {
|
||||||
pubkey String
|
pubkey String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The SigningCondition layer is the MANUAL-OVERRIDE source of truth
|
||||||
|
// (web-approval / add_signing_condition / create_account bootstrap) — it is
|
||||||
|
// no longer materialized from token policies (see aiolabs/nsecbunkerd#25:
|
||||||
|
// applyToken stopped photocopying; token grants are evaluated live off
|
||||||
|
// Token -> Policy -> PolicyRule). Under D1 the override layer carries its
|
||||||
|
// own lifecycle so it runs through the same grantIsLive(now) predicate as
|
||||||
|
// token grants.
|
||||||
model SigningCondition {
|
model SigningCondition {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
method String?
|
method String?
|
||||||
kind String?
|
kind String?
|
||||||
content String?
|
content String?
|
||||||
keyUserKeyName String?
|
keyUserKeyName String?
|
||||||
allowed Boolean?
|
allowed Boolean?
|
||||||
keyUserId Int?
|
keyUserId Int?
|
||||||
KeyUser KeyUser? @relation(fields: [keyUserId], references: [id])
|
createdAt DateTime @default(now())
|
||||||
|
expiresAt DateTime?
|
||||||
|
revokedAt DateTime?
|
||||||
|
KeyUser KeyUser? @relation(fields: [keyUserId], references: [id])
|
||||||
}
|
}
|
||||||
|
|
||||||
model Log {
|
model Log {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue