@cotal-ai/core 0.26.0 → 0.28.0-hack-20260823202734
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.
- package/dist/agent-file.d.ts.map +1 -1
- package/dist/agent-file.js +19 -3
- package/dist/agent-file.js.map +1 -1
- package/dist/endpoint.d.ts +20 -4
- package/dist/endpoint.d.ts.map +1 -1
- package/dist/endpoint.js +221 -14
- package/dist/endpoint.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/signing-key-rotation.d.ts +85 -0
- package/dist/signing-key-rotation.d.ts.map +1 -0
- package/dist/signing-key-rotation.js +85 -0
- package/dist/signing-key-rotation.js.map +1 -0
- package/package.json +2 -1
package/dist/index.d.ts
CHANGED
|
@@ -29,6 +29,7 @@ export * from "./session-terminal-frames.js";
|
|
|
29
29
|
export * from "./endpoint-virtual.js";
|
|
30
30
|
export * from "./endpoint-receipt.js";
|
|
31
31
|
export * from "./endpoint-signing.js";
|
|
32
|
+
export * from "./signing-key-rotation.js";
|
|
32
33
|
export * from "./endpoint-traits.js";
|
|
33
34
|
export * from "./safe-pattern.js";
|
|
34
35
|
export * from "./resolve.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,wBAAwB,CAAC;AACvC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,sBAAsB,CAAC;AACrC,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,wBAAwB,CAAC;AACvC,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,wBAAwB,CAAC;AACvC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,sBAAsB,CAAC;AACrC,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,wBAAwB,CAAC;AACvC,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -29,6 +29,7 @@ export * from "./session-terminal-frames.js";
|
|
|
29
29
|
export * from "./endpoint-virtual.js";
|
|
30
30
|
export * from "./endpoint-receipt.js";
|
|
31
31
|
export * from "./endpoint-signing.js";
|
|
32
|
+
export * from "./signing-key-rotation.js";
|
|
32
33
|
export * from "./endpoint-traits.js";
|
|
33
34
|
export * from "./safe-pattern.js";
|
|
34
35
|
export * from "./resolve.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,wBAAwB,CAAC;AACvC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,sBAAsB,CAAC;AACrC,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,wBAAwB,CAAC;AACvC,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,wBAAwB,CAAC;AACvC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,sBAAsB,CAAC;AACrC,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,wBAAwB,CAAC;AACvC,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A signing key that outlives its own validity window.
|
|
3
|
+
*
|
|
4
|
+
* A signer minted at boot with `validTo: Date.now() + TTL` is correct for exactly TTL and then
|
|
5
|
+
* permanently broken. Nothing re-mints it, so a process that stays up past its window loses the
|
|
6
|
+
* plane that key serves - and no restart of the CLIENT helps, because the expiry is in the SERVER's
|
|
7
|
+
* frozen anchor. Observed three times in one day on a manager whose session key was minted at boot
|
|
8
|
+
* with a flat 24h window: at +34.5h every attach failed closed with "outside its validity window",
|
|
9
|
+
* and the only recovery anyone found was restarting the manager, which killed every live session.
|
|
10
|
+
*
|
|
11
|
+
* Failing closed on an expired key is correct (SPEC 13.10) and is not what this changes. What this
|
|
12
|
+
* changes is that the key stops expiring unattended: it is renewed well before the edge, and the
|
|
13
|
+
* previous key stays verifiable for an overlap so artifacts signed a moment before the swap do not
|
|
14
|
+
* become unverifiable a moment after it.
|
|
15
|
+
*
|
|
16
|
+
* The rule follows the auth issuer, which has had it all along: sign with the newest key, verify
|
|
17
|
+
* against any live key, drop a key only once nothing it signed can still be in flight.
|
|
18
|
+
*/
|
|
19
|
+
import type { AnchorRole, SignerAnchor } from "./endpoint-signing.js";
|
|
20
|
+
/** Renew once this fraction of the window has elapsed. A third leaves two thirds of the window as
|
|
21
|
+
* margin, so a renewal can fail and be retried many times before anything expires. */
|
|
22
|
+
export declare const RENEW_AT_FRACTION: number;
|
|
23
|
+
/** How long a replaced key stays verifiable. It must exceed the longest artifact TTL that key can
|
|
24
|
+
* sign, or a grant issued just before the swap outlives the ability to verify it. */
|
|
25
|
+
export declare const OVERLAP_MS: number;
|
|
26
|
+
/** A key that can sign, paired with the anchor a verifier resolves for it. */
|
|
27
|
+
export interface KeyGeneration {
|
|
28
|
+
keyId: string;
|
|
29
|
+
keyPair: {
|
|
30
|
+
sign(input: Uint8Array): Uint8Array;
|
|
31
|
+
};
|
|
32
|
+
anchor: SignerAnchor;
|
|
33
|
+
}
|
|
34
|
+
/** Mint a fresh generation. Supplied by the caller so this module never owns key material. */
|
|
35
|
+
export type MintGeneration = (seq: number, now: number) => KeyGeneration;
|
|
36
|
+
/**
|
|
37
|
+
* When the current generation should be replaced.
|
|
38
|
+
*
|
|
39
|
+
* Deliberately a fraction of the window rather than a fixed lead time: a short-lived key and a
|
|
40
|
+
* long-lived one both get proportional margin, and a caller cannot accidentally configure a lead
|
|
41
|
+
* time longer than the window itself (which would renew on every single check).
|
|
42
|
+
*/
|
|
43
|
+
export declare function renewalDueAt(anchor: Pick<SignerAnchor, "validFrom" | "validTo">): number;
|
|
44
|
+
/** Whether `now` has reached the renewal point for this anchor. */
|
|
45
|
+
export declare function needsRenewal(anchor: Pick<SignerAnchor, "validFrom" | "validTo">, now: number): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* A rotating signing key: always signs with the newest generation, resolves any generation still
|
|
48
|
+
* inside its overlap, and drops the rest.
|
|
49
|
+
*
|
|
50
|
+
* The renewal is driven by {@link maybeRenew}, which the owner calls on a timer AND opportunistically
|
|
51
|
+
* before signing. Both matter: a timer alone stops if the event loop is starved or the process is
|
|
52
|
+
* suspended, and an opportunistic check alone never fires on an idle plane that then has to sign
|
|
53
|
+
* after a long quiet period. Together, an expired key requires both a dead timer and no signing.
|
|
54
|
+
*/
|
|
55
|
+
export declare class RotatingSigner {
|
|
56
|
+
#private;
|
|
57
|
+
constructor(mint: MintGeneration, now: number);
|
|
58
|
+
/** The generation to sign with: always the newest. */
|
|
59
|
+
current(): KeyGeneration;
|
|
60
|
+
/** Resolve an anchor by keyId across every generation still retained. A verifier must be able to
|
|
61
|
+
* check an artifact signed just before a swap, which is the entire point of the overlap. */
|
|
62
|
+
resolve(keyId: string): SignerAnchor | undefined;
|
|
63
|
+
/** Every generation currently retained, newest last. Observability for the owner's logs. */
|
|
64
|
+
generations(): readonly KeyGeneration[];
|
|
65
|
+
/**
|
|
66
|
+
* Renew if the current generation has reached its renewal point, then drop generations whose
|
|
67
|
+
* overlap has passed. Idempotent and cheap: safe to call before every signature.
|
|
68
|
+
*
|
|
69
|
+
* Returns the new keyId when a rotation happened, so the owner can log a fact rather than a hope.
|
|
70
|
+
*/
|
|
71
|
+
maybeRenew(now: number): string | undefined;
|
|
72
|
+
}
|
|
73
|
+
/** Build the anchor for a generation. Kept here so every caller gets the same window shape. */
|
|
74
|
+
export declare function generationAnchor(args: {
|
|
75
|
+
keyId: string;
|
|
76
|
+
publicKey: string;
|
|
77
|
+
owner: string;
|
|
78
|
+
roles: readonly AnchorRole[];
|
|
79
|
+
scope: SignerAnchor["scope"];
|
|
80
|
+
now: number;
|
|
81
|
+
ttlMs: number;
|
|
82
|
+
/** Tolerance for a verifier whose clock runs slightly behind this signer's. */
|
|
83
|
+
skewMs?: number;
|
|
84
|
+
}): SignerAnchor;
|
|
85
|
+
//# sourceMappingURL=signing-key-rotation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signing-key-rotation.d.ts","sourceRoot":"","sources":["../src/signing-key-rotation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAEtE;uFACuF;AACvF,eAAO,MAAM,iBAAiB,QAAQ,CAAC;AACvC;sFACsF;AACtF,eAAO,MAAM,UAAU,QAAiB,CAAC;AAEzC,8EAA8E;AAC9E,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE;QAAE,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,CAAA;KAAE,CAAC;IACjD,MAAM,EAAE,YAAY,CAAC;CACtB;AAED,8FAA8F;AAC9F,MAAM,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,aAAa,CAAC;AAEzE;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,WAAW,GAAG,SAAS,CAAC,GAAG,MAAM,CAGxF;AAED,mEAAmE;AACnE,wBAAgB,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,WAAW,GAAG,SAAS,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAEtG;AAED;;;;;;;;GAQG;AACH,qBAAa,cAAc;;gBAKb,IAAI,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM;IAK7C,sDAAsD;IACtD,OAAO,IAAI,aAAa;IAIxB;iGAC6F;IAC7F,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAIhD,4FAA4F;IAC5F,WAAW,IAAI,SAAS,aAAa,EAAE;IAIvC;;;;;OAKG;IACH,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;CAa5C;AAED,+FAA+F;AAC/F,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,SAAS,UAAU,EAAE,CAAC;IAC7B,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,CAAC;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,+EAA+E;IAC/E,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,YAAY,CAWf"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/** Renew once this fraction of the window has elapsed. A third leaves two thirds of the window as
|
|
2
|
+
* margin, so a renewal can fail and be retried many times before anything expires. */
|
|
3
|
+
export const RENEW_AT_FRACTION = 1 / 3;
|
|
4
|
+
/** How long a replaced key stays verifiable. It must exceed the longest artifact TTL that key can
|
|
5
|
+
* sign, or a grant issued just before the swap outlives the ability to verify it. */
|
|
6
|
+
export const OVERLAP_MS = 10 * 60 * 1000;
|
|
7
|
+
/**
|
|
8
|
+
* When the current generation should be replaced.
|
|
9
|
+
*
|
|
10
|
+
* Deliberately a fraction of the window rather than a fixed lead time: a short-lived key and a
|
|
11
|
+
* long-lived one both get proportional margin, and a caller cannot accidentally configure a lead
|
|
12
|
+
* time longer than the window itself (which would renew on every single check).
|
|
13
|
+
*/
|
|
14
|
+
export function renewalDueAt(anchor) {
|
|
15
|
+
const span = anchor.validTo - anchor.validFrom;
|
|
16
|
+
return anchor.validFrom + Math.floor(span * RENEW_AT_FRACTION);
|
|
17
|
+
}
|
|
18
|
+
/** Whether `now` has reached the renewal point for this anchor. */
|
|
19
|
+
export function needsRenewal(anchor, now) {
|
|
20
|
+
return now >= renewalDueAt(anchor);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* A rotating signing key: always signs with the newest generation, resolves any generation still
|
|
24
|
+
* inside its overlap, and drops the rest.
|
|
25
|
+
*
|
|
26
|
+
* The renewal is driven by {@link maybeRenew}, which the owner calls on a timer AND opportunistically
|
|
27
|
+
* before signing. Both matter: a timer alone stops if the event loop is starved or the process is
|
|
28
|
+
* suspended, and an opportunistic check alone never fires on an idle plane that then has to sign
|
|
29
|
+
* after a long quiet period. Together, an expired key requires both a dead timer and no signing.
|
|
30
|
+
*/
|
|
31
|
+
export class RotatingSigner {
|
|
32
|
+
#generations = [];
|
|
33
|
+
#seq = 0;
|
|
34
|
+
#mint;
|
|
35
|
+
constructor(mint, now) {
|
|
36
|
+
this.#mint = mint;
|
|
37
|
+
this.#generations.push(this.#mint(this.#seq++, now));
|
|
38
|
+
}
|
|
39
|
+
/** The generation to sign with: always the newest. */
|
|
40
|
+
current() {
|
|
41
|
+
return this.#generations[this.#generations.length - 1];
|
|
42
|
+
}
|
|
43
|
+
/** Resolve an anchor by keyId across every generation still retained. A verifier must be able to
|
|
44
|
+
* check an artifact signed just before a swap, which is the entire point of the overlap. */
|
|
45
|
+
resolve(keyId) {
|
|
46
|
+
return this.#generations.find((g) => g.keyId === keyId)?.anchor;
|
|
47
|
+
}
|
|
48
|
+
/** Every generation currently retained, newest last. Observability for the owner's logs. */
|
|
49
|
+
generations() {
|
|
50
|
+
return this.#generations;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Renew if the current generation has reached its renewal point, then drop generations whose
|
|
54
|
+
* overlap has passed. Idempotent and cheap: safe to call before every signature.
|
|
55
|
+
*
|
|
56
|
+
* Returns the new keyId when a rotation happened, so the owner can log a fact rather than a hope.
|
|
57
|
+
*/
|
|
58
|
+
maybeRenew(now) {
|
|
59
|
+
let rotated;
|
|
60
|
+
if (needsRenewal(this.current().anchor, now)) {
|
|
61
|
+
const next = this.#mint(this.#seq++, now);
|
|
62
|
+
this.#generations.push(next);
|
|
63
|
+
rotated = next.keyId;
|
|
64
|
+
}
|
|
65
|
+
// Never drop the newest, whatever the clock says: a plane with no signer cannot serve at all,
|
|
66
|
+
// and a key that is somehow already past its overlap is still better than nothing to sign with.
|
|
67
|
+
const newest = this.#generations[this.#generations.length - 1];
|
|
68
|
+
this.#generations = this.#generations.filter((g) => g === newest || now <= g.anchor.validTo + OVERLAP_MS);
|
|
69
|
+
return rotated;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/** Build the anchor for a generation. Kept here so every caller gets the same window shape. */
|
|
73
|
+
export function generationAnchor(args) {
|
|
74
|
+
const skew = args.skewMs ?? 60_000;
|
|
75
|
+
return {
|
|
76
|
+
keyId: args.keyId,
|
|
77
|
+
publicKey: args.publicKey,
|
|
78
|
+
owner: args.owner,
|
|
79
|
+
roles: args.roles,
|
|
80
|
+
scope: args.scope,
|
|
81
|
+
validFrom: args.now - skew,
|
|
82
|
+
validTo: args.now + args.ttlMs,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=signing-key-rotation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signing-key-rotation.js","sourceRoot":"","sources":["../src/signing-key-rotation.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cotal-ai/core",
|
|
3
3
|
"description": "The Cotal protocol: endpoint, subjects, message types, the NATS client layer, and the extension contracts.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.28.0-hack-20260823202734",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"cotal",
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
"@nats-io/jwt": "0.0.10-5",
|
|
46
46
|
"@nats-io/kv": "^3.4.0",
|
|
47
47
|
"@nats-io/obj": "^3.4.0",
|
|
48
|
+
"@nats-io/nats-core": "^3.4.0",
|
|
48
49
|
"@nats-io/nkeys": "^2.0.3",
|
|
49
50
|
"@nats-io/transport-node": "^3.4.0",
|
|
50
51
|
"ajv": "^8.20.0",
|