instar 1.3.1258 → 1.3.1259

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 (46) hide show
  1. package/dist/core/PasskeyCellActions.d.ts +73 -0
  2. package/dist/core/PasskeyCellActions.d.ts.map +1 -0
  3. package/dist/core/PasskeyCellActions.js +153 -0
  4. package/dist/core/PasskeyCellActions.js.map +1 -0
  5. package/dist/core/PasskeyCellMandate.d.ts +100 -0
  6. package/dist/core/PasskeyCellMandate.d.ts.map +1 -0
  7. package/dist/core/PasskeyCellMandate.js +143 -0
  8. package/dist/core/PasskeyCellMandate.js.map +1 -0
  9. package/dist/core/PasskeyGrantStore.d.ts +105 -0
  10. package/dist/core/PasskeyGrantStore.d.ts.map +1 -0
  11. package/dist/core/PasskeyGrantStore.js +243 -0
  12. package/dist/core/PasskeyGrantStore.js.map +1 -0
  13. package/dist/core/PasskeyIssuerSet.d.ts +67 -0
  14. package/dist/core/PasskeyIssuerSet.d.ts.map +1 -0
  15. package/dist/core/PasskeyIssuerSet.js +116 -0
  16. package/dist/core/PasskeyIssuerSet.js.map +1 -0
  17. package/dist/core/PasskeyNonceLedger.d.ts +58 -0
  18. package/dist/core/PasskeyNonceLedger.d.ts.map +1 -0
  19. package/dist/core/PasskeyNonceLedger.js +127 -0
  20. package/dist/core/PasskeyNonceLedger.js.map +1 -0
  21. package/dist/core/PostUpdateMigrator.d.ts +1 -1
  22. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  23. package/dist/core/PostUpdateMigrator.js +13 -1
  24. package/dist/core/PostUpdateMigrator.js.map +1 -1
  25. package/dist/core/WriteDomainRegistry.d.ts.map +1 -1
  26. package/dist/core/WriteDomainRegistry.js +60 -0
  27. package/dist/core/WriteDomainRegistry.js.map +1 -1
  28. package/dist/core/devGatedFeatures.d.ts.map +1 -1
  29. package/dist/core/devGatedFeatures.js +6 -0
  30. package/dist/core/devGatedFeatures.js.map +1 -1
  31. package/dist/data/standards-guard-index.json +1 -1
  32. package/dist/data/standards-guard-index.meta.json +2 -2
  33. package/dist/data/standards-registry.meta.json +1 -1
  34. package/dist/server/CapabilityIndex.js +1 -1
  35. package/dist/server/CapabilityIndex.js.map +1 -1
  36. package/dist/server/routes.d.ts +10 -0
  37. package/dist/server/routes.d.ts.map +1 -1
  38. package/dist/server/routes.js +315 -0
  39. package/dist/server/routes.js.map +1 -1
  40. package/package.json +1 -1
  41. package/src/data/builtin-manifest.json +63 -63
  42. package/src/data/standards-guard-index.json +1 -1
  43. package/src/data/standards-guard-index.meta.json +2 -2
  44. package/src/data/standards-registry.meta.json +1 -1
  45. package/upgrades/1.3.1259.md +40 -0
  46. package/upgrades/side-effects/passkey-inc5-grants-mandate.md +256 -0
@@ -0,0 +1,73 @@
1
+ /**
2
+ * PasskeyCellActions — apply a VERIFIED `passkey-cell` mandate (or the equivalent local PIN action)
3
+ * on THIS machine (spec docs/specs/agent-held-google-passkey.md §3.2 / §3.3).
4
+ *
5
+ * One funnel for both origins so the PIN route and the mesh receiver cannot drift: the route
6
+ * verifies the PIN and calls `applyLocal`; the receiver verifies the signature/issuer/nonce and
7
+ * calls `applyMandate`. Every application follows the same discipline:
8
+ * 1. write the nonce as RECEIVED — for a revoke together with the cutoff it will apply — BEFORE
9
+ * touching authority (crash-safe: a boot sweep can finish a revoke left `received`);
10
+ * 2. act idempotently (grant / revoke / issuer-add / issuer-remove / revert-method);
11
+ * 3. read back, then mark the nonce APPLIED.
12
+ * A duplicate of a `received` revoke re-applies the STORED cutoff (never recomputed); a duplicate of
13
+ * an `applied` op answers applied without acting.
14
+ *
15
+ * Ops this build applies: grant, revoke (local cell), issuer-add, issuer-remove, revert-method.
16
+ * The remaining ops verify but answer `op-not-available-on-this-build` — they land with the
17
+ * enrollment / health / suspension increments of the same run.
18
+ */
19
+ import type { PasskeyGrantStore, PasskeyGrant } from './PasskeyGrantStore.js';
20
+ import type { PasskeyIssuerSet } from './PasskeyIssuerSet.js';
21
+ import type { PasskeyNonceLedger } from './PasskeyNonceLedger.js';
22
+ import { type PasskeyCellMandateBody, type PasskeyCellVerifyDeps, type PasskeyCellVerifyResult } from './PasskeyCellMandate.js';
23
+ export interface RevertMethodOutcome {
24
+ reverted: boolean;
25
+ to?: string;
26
+ reason?: string;
27
+ bindingMissing?: boolean;
28
+ }
29
+ export interface PasskeyCellActionDeps {
30
+ selfMachineId: string;
31
+ grants: PasskeyGrantStore;
32
+ issuers: PasskeyIssuerSet;
33
+ nonces: PasskeyNonceLedger;
34
+ /** Revert every browser-profile account for the email back to its prior method (registry-owned). */
35
+ revertMethod: (canonicalEmail: string) => RevertMethodOutcome[];
36
+ /** Drop the passkey binding + credential for the cell after a revoke; returns what changed (names only). */
37
+ onRevoked?: (canonicalEmail: string, covered: PasskeyGrant[]) => {
38
+ changed: string[];
39
+ } | Promise<{
40
+ changed: string[];
41
+ }>;
42
+ /** True when at least one OTHER machine is registered and active (issuer bootstrap applies). */
43
+ hasActivePeers: () => boolean;
44
+ log?: (line: string) => void;
45
+ }
46
+ export type PasskeyCellApplyResult = {
47
+ applied: true;
48
+ op: string;
49
+ duplicate?: boolean;
50
+ result: Record<string, unknown>;
51
+ } | {
52
+ applied: false;
53
+ op: string;
54
+ reason: string;
55
+ detail?: Record<string, unknown>;
56
+ };
57
+ /** Apply a mandate that already passed `verifyPasskeyCellMandate` (or handle its replay honestly). */
58
+ export declare function applyVerifiedPasskeyCellMandate(deps: PasskeyCellActionDeps, verified: PasskeyCellVerifyResult, issuerMachineIdForReplay?: string): Promise<PasskeyCellApplyResult>;
59
+ /** The PIN route's entry: the same funnel, the local machine as issuer, no signature to check. */
60
+ export declare function applyLocalPasskeyCellAction(deps: PasskeyCellActionDeps, body: PasskeyCellMandateBody): Promise<PasskeyCellApplyResult>;
61
+ /** Verify + apply in one step (the receiver route). */
62
+ export declare function receivePasskeyCellMandate(deps: PasskeyCellActionDeps & {
63
+ verify: Omit<PasskeyCellVerifyDeps, 'selfMachineId' | 'issuers' | 'nonces'>;
64
+ }, portable: unknown): Promise<PasskeyCellApplyResult>;
65
+ /**
66
+ * Boot sweep (spec §3.2): finish every revoke left `received` (a crash between the ledger write and
67
+ * the read-back). Re-applies each one's STORED cutoff and marks it applied. Idempotent; call before
68
+ * any grant-writing route acts (the routes call it when they build the authority set).
69
+ */
70
+ export declare function sweepReceivedPasskeyRevokes(deps: PasskeyCellActionDeps): Promise<{
71
+ finished: string[];
72
+ }>;
73
+ //# sourceMappingURL=PasskeyCellActions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PasskeyCellActions.d.ts","sourceRoot":"","sources":["../../src/core/PasskeyCellActions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC9E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,KAAK,EAAE,kBAAkB,EAAe,MAAM,yBAAyB,CAAC;AAC/E,OAAO,EAA4C,KAAK,sBAAsB,EAAE,KAAK,qBAAqB,EAAE,KAAK,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAE1K,MAAM,WAAW,mBAAmB;IAAG,QAAQ,EAAE,OAAO,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,OAAO,CAAA;CAAE;AAElH,MAAM,WAAW,qBAAqB;IACpC,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,OAAO,EAAE,gBAAgB,CAAC;IAC1B,MAAM,EAAE,kBAAkB,CAAC;IAC3B,oGAAoG;IACpG,YAAY,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,mBAAmB,EAAE,CAAC;IAChE,4GAA4G;IAC5G,SAAS,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK;QAAE,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IACxH,gGAAgG;IAChG,cAAc,EAAE,MAAM,OAAO,CAAC;IAC9B,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B;AAED,MAAM,MAAM,sBAAsB,GAC9B;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GACnF;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC;AAErF,sGAAsG;AACtG,wBAAsB,+BAA+B,CAAC,IAAI,EAAE,qBAAqB,EAAE,QAAQ,EAAE,uBAAuB,EAAE,wBAAwB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAOxL;AAED,kGAAkG;AAClG,wBAAsB,2BAA2B,CAAC,IAAI,EAAE,qBAAqB,EAAE,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAG5I;AAED,uDAAuD;AACvD,wBAAsB,yBAAyB,CAAC,IAAI,EAAE,qBAAqB,GAAG;IAAE,MAAM,EAAE,IAAI,CAAC,qBAAqB,EAAE,eAAe,GAAG,SAAS,GAAG,QAAQ,CAAC,CAAA;CAAE,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAGjN;AAoGD;;;;GAIG;AACH,wBAAsB,2BAA2B,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAU9G"}
@@ -0,0 +1,153 @@
1
+ import { passkeyCellKey, verifyPasskeyCellMandate } from './PasskeyCellMandate.js';
2
+ /** Apply a mandate that already passed `verifyPasskeyCellMandate` (or handle its replay honestly). */
3
+ export async function applyVerifiedPasskeyCellMandate(deps, verified, issuerMachineIdForReplay) {
4
+ if (!verified.ok) {
5
+ if (verified.reason === 'replay' && verified.replayOf)
6
+ return replay(deps, verified.replayOf);
7
+ return { applied: false, op: 'unknown', reason: verified.reason, ...(verified.detail ? { detail: { detail: verified.detail } } : {}) };
8
+ }
9
+ void issuerMachineIdForReplay;
10
+ return apply(deps, verified.body, verified.issuerMachineId, 'mandate');
11
+ }
12
+ /** The PIN route's entry: the same funnel, the local machine as issuer, no signature to check. */
13
+ export async function applyLocalPasskeyCellAction(deps, body) {
14
+ if (body.targetMachineId !== deps.selfMachineId)
15
+ return { applied: false, op: body.op, reason: 'target-not-this-machine' };
16
+ return apply(deps, body, deps.selfMachineId, 'local-pin');
17
+ }
18
+ /** Verify + apply in one step (the receiver route). */
19
+ export async function receivePasskeyCellMandate(deps, portable) {
20
+ const verified = verifyPasskeyCellMandate(portable, { ...deps.verify, selfMachineId: deps.selfMachineId, issuers: deps.issuers, nonces: deps.nonces });
21
+ return applyVerifiedPasskeyCellMandate(deps, verified);
22
+ }
23
+ async function replay(deps, nonce) {
24
+ const rec = deps.nonces.get(nonce);
25
+ if (!rec)
26
+ return { applied: false, op: 'unknown', reason: 'replay' };
27
+ if (rec.state === 'applied')
28
+ return { applied: true, op: rec.op, duplicate: true, result: { nonce: rec.nonce, appliedAt: rec.appliedAt ?? null } };
29
+ if (rec.state === 'dismissed')
30
+ return { applied: false, op: rec.op, reason: 'dismissed-by-operator' };
31
+ if (rec.op === 'revoke') {
32
+ // A received-but-unapplied revoke: re-run the idempotent delete with the STORED cutoff.
33
+ const email = rec.cellKey.slice(0, rec.cellKey.lastIndexOf('@'));
34
+ const r = deps.grants.revoke({ email, revokedBy: rec.issuerMachineId, nonce: rec.nonce, cutoffSeq: rec.appliedCutoffSeq ?? 0 });
35
+ const changed = (await deps.onRevoked?.(email, r.covered))?.changed ?? [];
36
+ deps.nonces.markApplied(rec.nonce);
37
+ return { applied: true, op: 'revoke', duplicate: true, result: { covered: r.covered.map((g) => g.localSeq), changed } };
38
+ }
39
+ return { applied: false, op: rec.op, reason: 'replay' };
40
+ }
41
+ async function apply(deps, body, issuerMachineId, origin) {
42
+ const cellKey = passkeyCellKey(body);
43
+ const record = (extra = {}) => deps.nonces.receive({
44
+ nonce: body.nonce, op: body.op, cellKey, issuerMachineId, expiresAt: body.expiresAt,
45
+ ...(body.replacesNonce ? { replacesNonce: body.replacesNonce } : {}), ...extra,
46
+ });
47
+ switch (body.op) {
48
+ case 'grant': {
49
+ if (!body.canonicalEmail)
50
+ return { applied: false, op: body.op, reason: 'email-required' };
51
+ // FD21 — no trust-on-first-use: a multi-machine agent refuses its first grant until at least one
52
+ // peer issuer is confirmed on THIS machine's own dashboard.
53
+ if (deps.hasActivePeers() && deps.issuers.peerIssuers().length === 0) {
54
+ return { applied: false, op: body.op, reason: 'issuer-bootstrap-required', detail: { hint: 'confirm at least one peer issuer with the PIN on this machine (POST /passkeys/issuer-add) before the first grant' } };
55
+ }
56
+ const rec = record();
57
+ if (!rec.recorded)
58
+ return replay(deps, rec.existing.nonce);
59
+ const r = deps.grants.grant({ email: body.canonicalEmail, grantedBy: body.principal, origin: origin === 'mandate' ? 'mandate' : 'local-pin',
60
+ ...(typeof body.args.googleCreatedAt === 'string' ? { googleCreatedAt: body.args.googleCreatedAt } : {}) });
61
+ if (!deps.grants.has(body.canonicalEmail))
62
+ return { applied: false, op: body.op, reason: 'read-back-failed' };
63
+ deps.nonces.markApplied(body.nonce);
64
+ return { applied: true, op: body.op, result: { localSeq: r.grant.localSeq, created: r.created, grantedBy: r.grant.grantedBy } };
65
+ }
66
+ case 'revoke': {
67
+ if (!body.canonicalEmail)
68
+ return { applied: false, op: body.op, reason: 'email-required' };
69
+ // The cutoff written with the RECEIVED nonce is always CONCRETE: the issuer's sequence when it
70
+ // knew one, else the target's current active instance (0 = nothing to revoke). A replay then
71
+ // re-applies exactly this number — a grant made AFTER the first application can never be caught.
72
+ const seq = body.args.revokesGrantSeq;
73
+ const cutoff = Number.isSafeInteger(seq) ? seq : (deps.grants.get(body.canonicalEmail)?.localSeq ?? 0);
74
+ const rec = record({ appliedCutoffSeq: cutoff });
75
+ if (!rec.recorded)
76
+ return replay(deps, rec.existing.nonce);
77
+ const r = deps.grants.revoke({ email: body.canonicalEmail, revokedBy: body.principal, nonce: body.nonce, cutoffSeq: cutoff });
78
+ const changed = (await deps.onRevoked?.(body.canonicalEmail, r.covered))?.changed ?? [];
79
+ const reverted = deps.revertMethod(body.canonicalEmail);
80
+ if (deps.grants.get(body.canonicalEmail) && deps.grants.get(body.canonicalEmail).localSeq <= cutoff) {
81
+ return { applied: false, op: body.op, reason: 'read-back-failed' };
82
+ }
83
+ deps.nonces.markApplied(body.nonce);
84
+ return { applied: true, op: body.op, result: { covered: r.covered.map((g) => g.localSeq), nothingToRevoke: r.nothingToRevoke, appliedCutoffSeq: r.appliedCutoffSeq, changed, reverted,
85
+ note: 'this stops THIS agent\'s passkey path only; the profile\'s live session and any stored password remain' } };
86
+ }
87
+ case 'issuer-add': {
88
+ const machineId = typeof body.args.machineId === 'string' ? body.args.machineId.trim() : '';
89
+ if (!machineId)
90
+ return { applied: false, op: body.op, reason: 'machine-id-required' };
91
+ // Both origins: only a machine that is ACTIVE in THIS machine's registry can become an issuer.
92
+ if (deps.issuers.status(machineId) !== 'active')
93
+ return { applied: false, op: body.op, reason: 'machine-not-active', detail: { status: deps.issuers.status(machineId) } };
94
+ const rec = record();
95
+ if (!rec.recorded)
96
+ return replay(deps, rec.existing.nonce);
97
+ const r = deps.issuers.add({ machineId, addedVia: origin === 'mandate' ? 'issuer-add' : 'operator-confirmed', ...(origin === 'mandate' ? { addedByIssuer: issuerMachineId } : {}) });
98
+ if (!deps.issuers.isListed(machineId))
99
+ return { applied: false, op: body.op, reason: 'read-back-failed' };
100
+ deps.nonces.markApplied(body.nonce);
101
+ return { applied: true, op: body.op, result: { machineId, added: r.added } };
102
+ }
103
+ case 'issuer-remove': {
104
+ const machineId = typeof body.args.machineId === 'string' ? body.args.machineId.trim() : '';
105
+ if (!machineId)
106
+ return { applied: false, op: body.op, reason: 'machine-id-required' };
107
+ const rec = record();
108
+ if (!rec.recorded)
109
+ return replay(deps, rec.existing.nonce);
110
+ const r = deps.issuers.remove(machineId);
111
+ if (deps.issuers.isListed(machineId))
112
+ return { applied: false, op: body.op, reason: 'read-back-failed' };
113
+ deps.nonces.markApplied(body.nonce);
114
+ return { applied: true, op: body.op, result: { machineId, removed: r.removed } };
115
+ }
116
+ case 'revert-method': {
117
+ if (!body.canonicalEmail)
118
+ return { applied: false, op: body.op, reason: 'email-required' };
119
+ const rec = record();
120
+ if (!rec.recorded)
121
+ return replay(deps, rec.existing.nonce);
122
+ const reverted = deps.revertMethod(body.canonicalEmail);
123
+ deps.nonces.markApplied(body.nonce);
124
+ return { applied: true, op: body.op, result: { reverted } };
125
+ }
126
+ default: {
127
+ // Verified but not applicable on this build: record the nonce so a later replay is honest, and
128
+ // name the gap rather than pretending the op ran.
129
+ const rec = record();
130
+ if (!rec.recorded)
131
+ return replay(deps, rec.existing.nonce);
132
+ deps.log?.(`[passkey-cell] op ${body.op} verified but not available on this build (cell ${cellKey})`);
133
+ return { applied: false, op: body.op, reason: 'op-not-available-on-this-build' };
134
+ }
135
+ }
136
+ }
137
+ /**
138
+ * Boot sweep (spec §3.2): finish every revoke left `received` (a crash between the ledger write and
139
+ * the read-back). Re-applies each one's STORED cutoff and marks it applied. Idempotent; call before
140
+ * any grant-writing route acts (the routes call it when they build the authority set).
141
+ */
142
+ export async function sweepReceivedPasskeyRevokes(deps) {
143
+ const finished = [];
144
+ for (const rec of deps.nonces.receivedRevokes()) {
145
+ const email = rec.cellKey.slice(0, rec.cellKey.lastIndexOf('@'));
146
+ const r = deps.grants.revoke({ email, revokedBy: rec.issuerMachineId, nonce: rec.nonce, cutoffSeq: rec.appliedCutoffSeq ?? 0 });
147
+ await deps.onRevoked?.(email, r.covered);
148
+ deps.nonces.markApplied(rec.nonce);
149
+ finished.push(rec.nonce);
150
+ }
151
+ return { finished };
152
+ }
153
+ //# sourceMappingURL=PasskeyCellActions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PasskeyCellActions.js","sourceRoot":"","sources":["../../src/core/PasskeyCellActions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * PasskeyCellMandate — the signed, cross-machine authority carrier for agent-held Google passkeys
3
+ * (spec docs/specs/agent-held-google-passkey.md §3.3, FD12).
4
+ *
5
+ * A mandate type SEPARATE from `account-follow-me`. It reuses the WS5.2 idea (an Ed25519 signature
6
+ * by the issuing machine's identity key, bound to that machine's fingerprint) with three additions
7
+ * the follow-me scheme does not have: an EXPECTED-ISSUER SET on the receiver, a durable NONCE
8
+ * LEDGER, and a short EXPIRY (15 minutes, ±2 minutes skew) — except for `revoke`, which only removes
9
+ * authority and is deliberately exempt from expiry so it can be re-delivered for weeks unchanged.
10
+ *
11
+ * Isolation both ways is structural: the signed bytes carry a DISTINCT domain tag, so a follow-me
12
+ * issuance signature can never verify as a passkey-cell mandate and vice versa, whatever the
13
+ * payload claims; the follow-me consumer additionally refuses any bundle without an
14
+ * `account-follow-me` authority, and the passkey receiver refuses any body whose `type` is not
15
+ * `passkey-cell`.
16
+ *
17
+ * Pure logic over injected seams (issuer public-key resolver, issuer set, nonce ledger, clock) so
18
+ * the authority decision is unit-testable without a server. The dashboard PIN never appears here.
19
+ */
20
+ import crypto from 'node:crypto';
21
+ import type { PasskeyIssuerSet, IssuerTrustVerdict } from './PasskeyIssuerSet.js';
22
+ import type { PasskeyNonceLedger } from './PasskeyNonceLedger.js';
23
+ /** Domain-separation tag — never interchangeable with the follow-me issuance tag. */
24
+ export declare const PASSKEY_CELL_DOMAIN = "instar-passkey-cell-mandate-v1";
25
+ export declare const PASSKEY_MANDATE_TTL_MS: number;
26
+ export declare const PASSKEY_MANDATE_SKEW_MS: number;
27
+ export declare const PASSKEY_CELL_OPS: readonly ["grant", "revoke", "enroll", "prove", "adopt", "delete-legacy", "revert-method", "attest-google-removed", "suspend-now", "resume-suspension", "issuer-add", "issuer-remove", "exclude-peer", "include-peer", "recheck-chrome"];
28
+ export type PasskeyCellOp = typeof PASSKEY_CELL_OPS[number];
29
+ export interface PasskeyCellMandateBody {
30
+ v: 1;
31
+ type: 'passkey-cell';
32
+ /** The VERIFIED principal on the issuing machine (operator uid / dashboard-PIN identity). */
33
+ principal: string;
34
+ /** Canonical account email the op addresses ('' for non-cell ops such as issuer-add). */
35
+ canonicalEmail: string;
36
+ /** The machine that must act — the receiver refuses anything not addressed to itself. */
37
+ targetMachineId: string;
38
+ op: PasskeyCellOp;
39
+ /** Op arguments (e.g. revoke: { revokesGrantSeq?: number }; issuer-add: { machineId }). */
40
+ args: Record<string, unknown>;
41
+ issuedAt: string;
42
+ nonce: string;
43
+ expiresAt: string;
44
+ /** Re-signed revoke: the nonce of the copy it replaces (same principal/issuedAt/cutoff). */
45
+ replacesNonce?: string;
46
+ }
47
+ export interface PasskeyCellSignature {
48
+ alg: 'ed25519';
49
+ /** The issuing machine's id/fingerprint — bound into the signed bytes. */
50
+ issuerFingerprint: string;
51
+ sig: string;
52
+ }
53
+ export interface PortablePasskeyCellMandate {
54
+ body: PasskeyCellMandateBody;
55
+ signature: PasskeyCellSignature;
56
+ }
57
+ /** Deterministic canonical form: sorted keys, no whitespace. */
58
+ export declare function canonicalPasskeyCellBody(body: PasskeyCellMandateBody): string;
59
+ export declare function mintPasskeyCellBody(input: {
60
+ principal: string;
61
+ canonicalEmail: string;
62
+ targetMachineId: string;
63
+ op: PasskeyCellOp;
64
+ args?: Record<string, unknown>;
65
+ now?: number;
66
+ ttlMs?: number;
67
+ replacesNonce?: string;
68
+ }): PasskeyCellMandateBody;
69
+ /** Sign on the issuing machine with its Ed25519 identity private key. */
70
+ export declare function signPasskeyCellMandate(body: PasskeyCellMandateBody, issuerFingerprint: string, ed25519PrivateKey: crypto.KeyObject | string): PortablePasskeyCellMandate;
71
+ export type PasskeyCellVerifyReason = 'malformed' | 'not-a-passkey-cell-mandate' | 'unknown-op' | 'unsupported-alg' | 'issuer-not-trusted' | 'no-issuer-key' | 'bad-issuer-key' | 'bad-signature' | 'target-not-this-machine' | 'expired' | 'not-yet-valid' | 'replay' | 'nonce-ledger-unreadable' | 'ttl-too-long';
72
+ export type PasskeyCellVerifyResult = {
73
+ ok: true;
74
+ body: PasskeyCellMandateBody;
75
+ issuerMachineId: string;
76
+ } | {
77
+ ok: false;
78
+ reason: PasskeyCellVerifyReason;
79
+ detail?: string;
80
+ issuerVerdict?: IssuerTrustVerdict;
81
+ replayOf?: string;
82
+ };
83
+ export interface PasskeyCellVerifyDeps {
84
+ selfMachineId: string;
85
+ /** The REGISTERED Ed25519 public key (PEM) for a machine id, from THIS machine's registry — never from the payload. */
86
+ issuerPublicKeyPem: (machineId: string) => string | null;
87
+ issuers: Pick<PasskeyIssuerSet, 'verdict'>;
88
+ nonces: Pick<PasskeyNonceLedger, 'get'>;
89
+ now?: () => number;
90
+ skewMs?: number;
91
+ }
92
+ /**
93
+ * Verify a received mandate. FAILS CLOSED on every uncertainty. Order: shape → issuer trusted →
94
+ * signature → target → freshness (revoke exempt) → replay. Recording the nonce is the CALLER's
95
+ * step (it must be written together with the revoke cutoff BEFORE acting).
96
+ */
97
+ export declare function verifyPasskeyCellMandate(portable: unknown, deps: PasskeyCellVerifyDeps): PasskeyCellVerifyResult;
98
+ /** `<canonicalEmail>@<targetMachineId>` — the ledger's cell key. */
99
+ export declare function passkeyCellKey(body: Pick<PasskeyCellMandateBody, 'canonicalEmail' | 'targetMachineId'>): string;
100
+ //# sourceMappingURL=PasskeyCellMandate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PasskeyCellMandate.d.ts","sourceRoot":"","sources":["../../src/core/PasskeyCellMandate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,143 @@
1
+ /**
2
+ * PasskeyCellMandate — the signed, cross-machine authority carrier for agent-held Google passkeys
3
+ * (spec docs/specs/agent-held-google-passkey.md §3.3, FD12).
4
+ *
5
+ * A mandate type SEPARATE from `account-follow-me`. It reuses the WS5.2 idea (an Ed25519 signature
6
+ * by the issuing machine's identity key, bound to that machine's fingerprint) with three additions
7
+ * the follow-me scheme does not have: an EXPECTED-ISSUER SET on the receiver, a durable NONCE
8
+ * LEDGER, and a short EXPIRY (15 minutes, ±2 minutes skew) — except for `revoke`, which only removes
9
+ * authority and is deliberately exempt from expiry so it can be re-delivered for weeks unchanged.
10
+ *
11
+ * Isolation both ways is structural: the signed bytes carry a DISTINCT domain tag, so a follow-me
12
+ * issuance signature can never verify as a passkey-cell mandate and vice versa, whatever the
13
+ * payload claims; the follow-me consumer additionally refuses any bundle without an
14
+ * `account-follow-me` authority, and the passkey receiver refuses any body whose `type` is not
15
+ * `passkey-cell`.
16
+ *
17
+ * Pure logic over injected seams (issuer public-key resolver, issuer set, nonce ledger, clock) so
18
+ * the authority decision is unit-testable without a server. The dashboard PIN never appears here.
19
+ */
20
+ import crypto from 'node:crypto';
21
+ /** Domain-separation tag — never interchangeable with the follow-me issuance tag. */
22
+ export const PASSKEY_CELL_DOMAIN = 'instar-passkey-cell-mandate-v1';
23
+ export const PASSKEY_MANDATE_TTL_MS = 15 * 60_000;
24
+ export const PASSKEY_MANDATE_SKEW_MS = 2 * 60_000;
25
+ export const PASSKEY_CELL_OPS = [
26
+ 'grant', 'revoke', 'enroll', 'prove', 'adopt', 'delete-legacy', 'revert-method', 'attest-google-removed',
27
+ 'suspend-now', 'resume-suspension', 'issuer-add', 'issuer-remove', 'exclude-peer', 'include-peer', 'recheck-chrome',
28
+ ];
29
+ /** Deterministic canonical form: sorted keys, no whitespace. */
30
+ export function canonicalPasskeyCellBody(body) {
31
+ const sortKeys = (v) => {
32
+ if (Array.isArray(v))
33
+ return v.map(sortKeys);
34
+ if (v && typeof v === 'object') {
35
+ return Object.fromEntries(Object.keys(v).sort().map((k) => [k, sortKeys(v[k])]));
36
+ }
37
+ return v;
38
+ };
39
+ return JSON.stringify(sortKeys(body));
40
+ }
41
+ function signingInput(canonical, issuerFingerprint) {
42
+ return Buffer.from(`${PASSKEY_CELL_DOMAIN}\x1f${issuerFingerprint}\x1f${canonical}`, 'utf-8');
43
+ }
44
+ export function mintPasskeyCellBody(input) {
45
+ const now = input.now ?? Date.now();
46
+ return {
47
+ v: 1, type: 'passkey-cell', principal: input.principal, canonicalEmail: input.canonicalEmail.trim().toLowerCase(),
48
+ targetMachineId: input.targetMachineId, op: input.op, args: input.args ?? {},
49
+ issuedAt: new Date(now).toISOString(), nonce: crypto.randomUUID(),
50
+ expiresAt: new Date(now + (input.ttlMs ?? PASSKEY_MANDATE_TTL_MS)).toISOString(),
51
+ ...(input.replacesNonce ? { replacesNonce: input.replacesNonce } : {}),
52
+ };
53
+ }
54
+ /** Sign on the issuing machine with its Ed25519 identity private key. */
55
+ export function signPasskeyCellMandate(body, issuerFingerprint, ed25519PrivateKey) {
56
+ if (!issuerFingerprint)
57
+ throw new Error('passkey-cell-issuer-fingerprint-required');
58
+ if (body.type !== 'passkey-cell' || !PASSKEY_CELL_OPS.includes(body.op))
59
+ throw new Error('passkey-cell-body-invalid');
60
+ const key = typeof ed25519PrivateKey === 'string' ? crypto.createPrivateKey(ed25519PrivateKey) : ed25519PrivateKey;
61
+ const sig = crypto.sign(null, signingInput(canonicalPasskeyCellBody(body), issuerFingerprint), key);
62
+ return { body, signature: { alg: 'ed25519', issuerFingerprint, sig: sig.toString('base64') } };
63
+ }
64
+ /**
65
+ * Verify a received mandate. FAILS CLOSED on every uncertainty. Order: shape → issuer trusted →
66
+ * signature → target → freshness (revoke exempt) → replay. Recording the nonce is the CALLER's
67
+ * step (it must be written together with the revoke cutoff BEFORE acting).
68
+ */
69
+ export function verifyPasskeyCellMandate(portable, deps) {
70
+ const p = portable;
71
+ if (!p || typeof p !== 'object' || !p.body || typeof p.body !== 'object' || !p.signature || typeof p.signature !== 'object') {
72
+ return { ok: false, reason: 'malformed' };
73
+ }
74
+ const body = p.body;
75
+ const signature = p.signature;
76
+ if (body.v !== 1 || body.type !== 'passkey-cell')
77
+ return { ok: false, reason: 'not-a-passkey-cell-mandate' };
78
+ if (!PASSKEY_CELL_OPS.includes(body.op))
79
+ return { ok: false, reason: 'unknown-op', detail: String(body.op) };
80
+ for (const f of ['principal', 'targetMachineId', 'issuedAt', 'nonce', 'expiresAt']) {
81
+ if (typeof body[f] !== 'string' || !body[f])
82
+ return { ok: false, reason: 'malformed', detail: f };
83
+ }
84
+ if (typeof body.canonicalEmail !== 'string' || !body.args || typeof body.args !== 'object')
85
+ return { ok: false, reason: 'malformed' };
86
+ if (signature.alg !== 'ed25519' || typeof signature.issuerFingerprint !== 'string' || typeof signature.sig !== 'string') {
87
+ return { ok: false, reason: 'unsupported-alg' };
88
+ }
89
+ // Expected-issuer set FIRST: an untrusted issuer is refused before any key lookup (no TOFU).
90
+ const issuerVerdict = deps.issuers.verdict(signature.issuerFingerprint);
91
+ if (!issuerVerdict.trusted)
92
+ return { ok: false, reason: 'issuer-not-trusted', issuerVerdict };
93
+ const pem = deps.issuerPublicKeyPem(signature.issuerFingerprint);
94
+ if (!pem)
95
+ return { ok: false, reason: 'no-issuer-key' };
96
+ let pub;
97
+ try {
98
+ pub = crypto.createPublicKey(pem);
99
+ }
100
+ catch (err) {
101
+ return { ok: false, reason: 'bad-issuer-key', detail: err instanceof Error ? err.message : String(err) };
102
+ }
103
+ let valid = false;
104
+ try {
105
+ valid = crypto.verify(null, signingInput(canonicalPasskeyCellBody(body), signature.issuerFingerprint), pub, Buffer.from(signature.sig, 'base64'));
106
+ }
107
+ catch {
108
+ valid = false;
109
+ }
110
+ if (!valid)
111
+ return { ok: false, reason: 'bad-signature' };
112
+ if (body.targetMachineId !== deps.selfMachineId)
113
+ return { ok: false, reason: 'target-not-this-machine' };
114
+ const now = (deps.now ?? Date.now)();
115
+ const skew = deps.skewMs ?? PASSKEY_MANDATE_SKEW_MS;
116
+ const issued = Date.parse(body.issuedAt);
117
+ const expires = Date.parse(body.expiresAt);
118
+ if (!Number.isFinite(issued) || !Number.isFinite(expires))
119
+ return { ok: false, reason: 'malformed', detail: 'timestamps' };
120
+ if (issued - skew > now)
121
+ return { ok: false, reason: 'not-yet-valid' };
122
+ // Defence in depth: an issuer cannot mint a long-lived mandate by stretching expiresAt.
123
+ if (expires - issued > PASSKEY_MANDATE_TTL_MS + skew)
124
+ return { ok: false, reason: 'ttl-too-long' };
125
+ // Only `revoke` is exempt from expiry (it can only remove authority); every other op must be fresh.
126
+ if (body.op !== 'revoke' && now > expires + skew)
127
+ return { ok: false, reason: 'expired' };
128
+ let seen;
129
+ try {
130
+ seen = deps.nonces.get(body.nonce) ?? (body.replacesNonce ? deps.nonces.get(body.replacesNonce) : null);
131
+ }
132
+ catch (err) {
133
+ return { ok: false, reason: 'nonce-ledger-unreadable', detail: err instanceof Error ? err.message : String(err) };
134
+ }
135
+ if (seen)
136
+ return { ok: false, reason: 'replay', replayOf: seen.nonce };
137
+ return { ok: true, body, issuerMachineId: signature.issuerFingerprint };
138
+ }
139
+ /** `<canonicalEmail>@<targetMachineId>` — the ledger's cell key. */
140
+ export function passkeyCellKey(body) {
141
+ return `${body.canonicalEmail}@${body.targetMachineId}`;
142
+ }
143
+ //# sourceMappingURL=PasskeyCellMandate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PasskeyCellMandate.js","sourceRoot":"","sources":["../../src/core/PasskeyCellMandate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,105 @@
1
+ export declare const PASSKEY_GRANTS_FILE: string;
2
+ export declare const PASSKEY_REVOKE_HWM_FILE: string;
3
+ export interface PasskeyGrant {
4
+ /** Canonical (lower-cased, trimmed) account email. */
5
+ canonicalEmail: string;
6
+ /** The machine this grant authorizes — always THIS machine for a locally held grant. */
7
+ machineId: string;
8
+ /** The VERIFIED principal who granted (operator uid / issuer machine), never a content name. */
9
+ grantedBy: string;
10
+ grantedAt: string;
11
+ /** Monotonic per-machine grant-instance sequence. */
12
+ localSeq: number;
13
+ /** How the grant arrived: the local PIN route, a verified passkey-cell mandate, or a restore. */
14
+ origin: 'local-pin' | 'mandate' | 'restore';
15
+ /** Google's creation timestamp for the passkey once known (names the entry for removal). */
16
+ googleCreatedAt?: string;
17
+ status: 'active' | 'revoked';
18
+ revokedAt?: string;
19
+ revokedBy?: string;
20
+ /** The revoke nonce that covered this instance (audit link to the nonce ledger). */
21
+ revokeNonce?: string;
22
+ }
23
+ /** A non-secret local copy of a grant THIS machine issued to a PEER (so a revoke can name it). */
24
+ export interface IssuedPeerGrant {
25
+ canonicalEmail: string;
26
+ targetMachineId: string;
27
+ issuedAt: string;
28
+ nonce: string;
29
+ /** The target's `localSeq` once it acknowledged the grant. */
30
+ targetLocalSeq?: number;
31
+ googleCreatedAt?: string;
32
+ }
33
+ export interface PasskeyGrantStoreOptions {
34
+ stateDir: string;
35
+ machineId: string;
36
+ now?: () => number;
37
+ }
38
+ export interface RevokeResult {
39
+ /** Grant instances this revoke covered (status flipped to revoked). */
40
+ covered: PasskeyGrant[];
41
+ /** The cutoff actually applied (largest localSeq covered), for the nonce ledger's stored cutoff. */
42
+ appliedCutoffSeq: number | null;
43
+ /** True when no active instance existed — idempotent no-op. */
44
+ nothingToRevoke: boolean;
45
+ }
46
+ export declare function canonicalEmail(value: string): string;
47
+ export declare class PasskeyGrantStore {
48
+ private readonly file;
49
+ private readonly hwmFile;
50
+ private readonly machineId;
51
+ private readonly now;
52
+ constructor(opts: PasskeyGrantStoreOptions);
53
+ list(): PasskeyGrant[];
54
+ /** The ACTIVE grant for a cell on this machine, or null. */
55
+ get(email: string): PasskeyGrant | null;
56
+ has(email: string): boolean;
57
+ listIssuedPeerGrants(): IssuedPeerGrant[];
58
+ /** The largest localSeq ever revoked on this machine (0 when none). */
59
+ revokeHighWater(): number;
60
+ /**
61
+ * Grant the cell (email × THIS machine). Exactly one ACTIVE instance per cell: an existing active
62
+ * grant is returned unchanged (idempotent); a previously revoked cell gets a NEW instance with a
63
+ * fresh, higher `localSeq` — which is what lets a later legitimate re-grant survive an older revoke.
64
+ */
65
+ grant(input: {
66
+ email: string;
67
+ grantedBy: string;
68
+ origin: PasskeyGrant['origin'];
69
+ googleCreatedAt?: string;
70
+ }): {
71
+ grant: PasskeyGrant;
72
+ created: boolean;
73
+ };
74
+ /** Record Google's creation timestamp on the active grant once the mint reports it. */
75
+ setGoogleCreatedAt(email: string, googleCreatedAt: string): boolean;
76
+ /**
77
+ * Revoke the cell's grant instances with `localSeq ≤ cutoffSeq`. With `cutoffSeq` absent the
78
+ * revoke covers EVERY instance present now (the issuer did not know the target's sequence —
79
+ * restrictive by design). Raises the revoke high-water mark. Idempotent.
80
+ */
81
+ revoke(input: {
82
+ email: string;
83
+ revokedBy: string;
84
+ nonce: string;
85
+ cutoffSeq?: number | null;
86
+ }): RevokeResult;
87
+ /** Keep a local, non-secret copy of a grant issued to a peer (so a revoke can always name it). */
88
+ recordIssuedPeerGrant(entry: IssuedPeerGrant): void;
89
+ /** Drop a peer-grant copy once the peer's revoke is applied and acknowledged. */
90
+ forgetIssuedPeerGrant(email: string, targetMachineId: string): boolean;
91
+ /**
92
+ * Restore-time guard (§6): a grant carried in from a backup with `localSeq` at or below the local
93
+ * revoke high-water mark is DROPPED — the revoke outlives the restore. Returns what was kept.
94
+ */
95
+ admitRestoredGrants(candidates: PasskeyGrant[]): {
96
+ kept: PasskeyGrant[];
97
+ dropped: PasskeyGrant[];
98
+ };
99
+ private read;
100
+ private write;
101
+ private raiseHighWater;
102
+ /** Test/maintenance helper: remove the store files through the audited fs funnel. */
103
+ static removeFiles(stateDir: string): void;
104
+ }
105
+ //# sourceMappingURL=PasskeyGrantStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PasskeyGrantStore.d.ts","sourceRoot":"","sources":["../../src/core/PasskeyGrantStore.ts"],"names":[],"mappings":"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"}