@hraness/oh 0.3.0 → 0.3.1
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/README.md +12 -9
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +1 -1
- package/dist/libsql-semantic.d.ts +19 -0
- package/dist/libsql-semantic.d.ts.map +1 -1
- package/dist/semantic-cloud.js +27 -0
- package/package.json +1 -1
- package/skills/oh/SKILL.md +2 -2
- package/spec/v1/semantic-cloud.md +9 -0
- package/src/cli.ts +1 -1
- package/src/libsql-semantic.test.ts +107 -0
- package/src/libsql-semantic.ts +51 -0
package/README.md
CHANGED
|
@@ -40,16 +40,16 @@ direct libSQL authority also support Node 24 serverless runtimes. Install the
|
|
|
40
40
|
exact current release from npm:
|
|
41
41
|
|
|
42
42
|
```sh
|
|
43
|
-
bun add --global @hraness/oh@0.3.
|
|
43
|
+
bun add --global @hraness/oh@0.3.1
|
|
44
44
|
oh --help
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
The identical package bytes and their checksum are available from the
|
|
48
|
-
[immutable GitHub Release](https://github.com/hraness/oh/releases/tag/v0.3.
|
|
48
|
+
[immutable GitHub Release](https://github.com/hraness/oh/releases/tag/v0.3.1),
|
|
49
49
|
including the mirrored
|
|
50
|
-
[`hraness-oh-0.3.
|
|
50
|
+
[`hraness-oh-0.3.1.tgz`](https://github.com/hraness/oh/releases/download/v0.3.1/hraness-oh-0.3.1.tgz)
|
|
51
51
|
and
|
|
52
|
-
[`SHA256SUMS`](https://github.com/hraness/oh/releases/download/v0.3.
|
|
52
|
+
[`SHA256SUMS`](https://github.com/hraness/oh/releases/download/v0.3.1/SHA256SUMS).
|
|
53
53
|
|
|
54
54
|
Oh writes to `.oh/oh.sqlite` and the `default` space unless you select another
|
|
55
55
|
path or space. Keep `.oh/` out of source control.
|
|
@@ -107,7 +107,7 @@ For a project dependency, pin the same immutable release in `package.json`:
|
|
|
107
107
|
```json
|
|
108
108
|
{
|
|
109
109
|
"dependencies": {
|
|
110
|
-
"@hraness/oh": "0.3.
|
|
110
|
+
"@hraness/oh": "0.3.1"
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
```
|
|
@@ -567,9 +567,12 @@ const staged = await cache.stage({
|
|
|
567
567
|
embeddingClient: embedder,
|
|
568
568
|
generation: snapshot.head.generation,
|
|
569
569
|
});
|
|
570
|
+
const published = await cache.publishedHead({
|
|
571
|
+
authorityId: "thread:research/epoch:1",
|
|
572
|
+
});
|
|
570
573
|
await cache.publish({
|
|
571
574
|
authorityId: "thread:research/epoch:1",
|
|
572
|
-
expectedPublishedGeneration: null,
|
|
575
|
+
expectedPublishedGeneration: published?.generation ?? null,
|
|
573
576
|
generation: staged.generation,
|
|
574
577
|
});
|
|
575
578
|
```
|
|
@@ -649,9 +652,9 @@ keep remote sync explicit.
|
|
|
649
652
|
You can also give an agent this prompt:
|
|
650
653
|
|
|
651
654
|
```text
|
|
652
|
-
Install @hraness/oh@0.3.
|
|
653
|
-
exact npm tarball and SHA256SUMS are mirrored by the immutable v0.3.
|
|
654
|
-
https://github.com/hraness/oh/releases/tag/v0.3.
|
|
655
|
+
Install @hraness/oh@0.3.1 from npm and use its packaged Oh Agent Skill. The
|
|
656
|
+
exact npm tarball and SHA256SUMS are mirrored by the immutable v0.3.1 Release at
|
|
657
|
+
https://github.com/hraness/oh/releases/tag/v0.3.1. Verify the CLI with
|
|
655
658
|
`oh --help` and `oh version`.
|
|
656
659
|
Do not create or modify an Oh database until I name its path and ask you to.
|
|
657
660
|
```
|
package/dist/cli.d.ts
CHANGED
package/dist/cli.js
CHANGED
|
@@ -49,6 +49,18 @@ export type OhSemanticPublishResultV1 = Readonly<{
|
|
|
49
49
|
published: boolean;
|
|
50
50
|
v: 1;
|
|
51
51
|
}>;
|
|
52
|
+
/** The exact, currently published cache pointer for one semantic authority. */
|
|
53
|
+
export type OhSemanticPublishedHeadV1 = Readonly<{
|
|
54
|
+
authorityId: string;
|
|
55
|
+
authoritySha256: Sha256Hex;
|
|
56
|
+
generation: number;
|
|
57
|
+
generationSha256: Sha256Hex;
|
|
58
|
+
membershipSha256: Sha256Hex;
|
|
59
|
+
profileSha256: Sha256Hex;
|
|
60
|
+
publishedAt: string;
|
|
61
|
+
rendererSha256: Sha256Hex;
|
|
62
|
+
v: 1;
|
|
63
|
+
}>;
|
|
52
64
|
export type OhSemanticSearchResultV1 = Readonly<{
|
|
53
65
|
chunkOrdinal: number;
|
|
54
66
|
key: string;
|
|
@@ -77,6 +89,13 @@ export declare class OhLibSqlSemanticCacheV1 {
|
|
|
77
89
|
/** @internal Public callers should use `openOhLibSqlSemanticCacheV1`. */
|
|
78
90
|
static open(client: OhLibSqlClientV1, closeClient: boolean): Promise<OhLibSqlSemanticCacheV1>;
|
|
79
91
|
close(): Promise<void>;
|
|
92
|
+
/**
|
|
93
|
+
* Reads the current compare-and-swap base without exposing cache rows or
|
|
94
|
+
* private source text. An absent or purged authority has no published head.
|
|
95
|
+
*/
|
|
96
|
+
publishedHead(input: Readonly<{
|
|
97
|
+
authorityId: string;
|
|
98
|
+
}>): Promise<OhSemanticPublishedHeadV1 | null>;
|
|
80
99
|
stage(input: Readonly<{
|
|
81
100
|
authorityId: string;
|
|
82
101
|
authoritySha256: Sha256Hex;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"libsql-semantic.d.ts","sourceRoot":"","sources":["../src/libsql-semantic.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"libsql-semantic.d.ts","sourceRoot":"","sources":["../src/libsql-semantic.ts"],"names":[],"mappings":"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"}
|
package/dist/semantic-cloud.js
CHANGED
|
@@ -1456,6 +1456,33 @@ class OhLibSqlSemanticCacheV1 {
|
|
|
1456
1456
|
if (this.#closeClient)
|
|
1457
1457
|
this.#client.close?.();
|
|
1458
1458
|
}
|
|
1459
|
+
async publishedHead(input) {
|
|
1460
|
+
this.#open();
|
|
1461
|
+
const authorityId = parseAuthorityId(input.authorityId);
|
|
1462
|
+
if (await readPurge(this.#client, authorityId) !== null)
|
|
1463
|
+
return null;
|
|
1464
|
+
const head = await readHead(this.#client, authorityId);
|
|
1465
|
+
if (head === null)
|
|
1466
|
+
return null;
|
|
1467
|
+
const generation = await readGeneration(this.#client, authorityId, head.generation);
|
|
1468
|
+
if (generation === null || !headMatchesGeneration(head, generation)) {
|
|
1469
|
+
if (await readPurge(this.#client, authorityId) !== null)
|
|
1470
|
+
return null;
|
|
1471
|
+
throw new OhLibSqlSemanticError("integrity", "The semantic published head does not match its immutable generation.");
|
|
1472
|
+
}
|
|
1473
|
+
if (await readPurge(this.#client, authorityId) !== null)
|
|
1474
|
+
return null;
|
|
1475
|
+
const finalHead = await readHead(this.#client, authorityId);
|
|
1476
|
+
if (finalHead === null) {
|
|
1477
|
+
if (await readPurge(this.#client, authorityId) !== null)
|
|
1478
|
+
return null;
|
|
1479
|
+
throw new OhLibSqlSemanticError("integrity", "The semantic published head disappeared during its read.");
|
|
1480
|
+
}
|
|
1481
|
+
if (canonicalJson(finalHead) !== canonicalJson(head)) {
|
|
1482
|
+
throw new OhLibSqlSemanticError("conflict", "The semantic published head changed during its read.");
|
|
1483
|
+
}
|
|
1484
|
+
return Object.freeze({ ...head, v: 1 });
|
|
1485
|
+
}
|
|
1459
1486
|
async stage(input) {
|
|
1460
1487
|
this.#open();
|
|
1461
1488
|
validateEmbeddingClient(input.embeddingClient);
|
package/package.json
CHANGED
package/skills/oh/SKILL.md
CHANGED
|
@@ -30,8 +30,8 @@ oh --help
|
|
|
30
30
|
oh version
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
The supported CLI is the exact npm release `@hraness/oh@0.3.
|
|
34
|
-
tarball and checksum are mirrored by the immutable GitHub Release `v0.3.
|
|
33
|
+
The supported CLI is the exact npm release `@hraness/oh@0.3.1`. Its identical
|
|
34
|
+
tarball and checksum are mirrored by the immutable GitHub Release `v0.3.1`.
|
|
35
35
|
It requires Bun 1.3.14 or newer. The versioned contract is published at
|
|
36
36
|
<https://oh.computer/spec/>.
|
|
37
37
|
|
|
@@ -68,6 +68,15 @@ hit is rejoined to a caller-supplied current authority record digest. A stale
|
|
|
68
68
|
head, changed authority digest, changed record digest, concurrent publish, or
|
|
69
69
|
purge returns no stale authority.
|
|
70
70
|
|
|
71
|
+
`publishedHead` returns the current compare-and-swap base or `null` for an
|
|
72
|
+
absent or purged authority. Its bounded projection contains only the authority,
|
|
73
|
+
generation, profile, renderer, membership, generation and publication
|
|
74
|
+
identities already held by the cache; it exposes no vector, record body or
|
|
75
|
+
formatted input. The read verifies that the pointer still matches its immutable
|
|
76
|
+
generation and fails with an integrity error on divergence. A host can read the
|
|
77
|
+
base, stage a later authoritative generation, and pass the returned generation
|
|
78
|
+
to `publish` without keeping a second cache pointer elsewhere.
|
|
79
|
+
|
|
71
80
|
`purgeAuthority` writes a permanent tombstone, removes the head, memberships,
|
|
72
81
|
and staged generations, then deletes vectors no remaining authority uses. The
|
|
73
82
|
tombstone prevents the same authority ID from being staged or published again.
|
package/src/cli.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { Oh } from "./sdk";
|
|
|
9
9
|
import { OH_SQLITE_SCHEMA_VERSION } from "./sqlite/migrations";
|
|
10
10
|
import { createOhSyncBundleV1, parseOhSyncBundleV1 } from "./sync";
|
|
11
11
|
|
|
12
|
-
export const OH_PACKAGE_VERSION = "0.3.
|
|
12
|
+
export const OH_PACKAGE_VERSION = "0.3.1" as const;
|
|
13
13
|
|
|
14
14
|
type ParsedArguments = { options: Map<string, string[]>; positionals: string[] };
|
|
15
15
|
type ValidatedInvocation = Readonly<{
|
|
@@ -85,6 +85,22 @@ class InterleavingLibSqlClient extends SqliteCompatibleLibSqlClient {
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
class PublishedHeadInterleavingLibSqlClient extends SqliteCompatibleLibSqlClient {
|
|
89
|
+
afterHeadRead: (() => Promise<void>) | null = null;
|
|
90
|
+
|
|
91
|
+
override async execute(statement: OhLibSqlStatementV1 | string): Promise<OhLibSqlResultV1> {
|
|
92
|
+
const result = await super.execute(statement);
|
|
93
|
+
const sql = typeof statement === "string" ? statement : statement.sql;
|
|
94
|
+
if (/FROM\s+oh_semantic_heads\s+WHERE\s+authority_id\s*=\s*\?/iu.test(sql)
|
|
95
|
+
&& this.afterHeadRead !== null) {
|
|
96
|
+
const hook = this.afterHeadRead;
|
|
97
|
+
this.afterHeadRead = null;
|
|
98
|
+
await hook();
|
|
99
|
+
}
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
88
104
|
const instant1 = "2026-08-31T12:00:00.000Z";
|
|
89
105
|
const instant2 = "2026-08-31T12:01:00.000Z";
|
|
90
106
|
const instant3 = "2026-08-31T12:02:00.000Z";
|
|
@@ -197,6 +213,7 @@ describe("libSQL derived semantic cache", () => {
|
|
|
197
213
|
document("memory:two", "needle-beta"),
|
|
198
214
|
] as const;
|
|
199
215
|
const firstAuthoritySha256 = digest("authority:first");
|
|
216
|
+
expect(await cache.publishedHead({ authorityId })).toBeNull();
|
|
200
217
|
const first = await cache.stage({
|
|
201
218
|
authorityId,
|
|
202
219
|
authoritySha256: firstAuthoritySha256,
|
|
@@ -207,6 +224,7 @@ describe("libSQL derived semantic cache", () => {
|
|
|
207
224
|
});
|
|
208
225
|
expect(first).toMatchObject({ chunks: 2, documents: 2, embedded: 2, reused: 0 });
|
|
209
226
|
expect(calls).toHaveLength(1);
|
|
227
|
+
expect(await cache.publishedHead({ authorityId })).toBeNull();
|
|
210
228
|
expect(await cache.stage({
|
|
211
229
|
authorityId,
|
|
212
230
|
authoritySha256: firstAuthoritySha256,
|
|
@@ -235,6 +253,17 @@ describe("libSQL derived semantic cache", () => {
|
|
|
235
253
|
generation: 1,
|
|
236
254
|
publishedAt: instant1,
|
|
237
255
|
})).toMatchObject({ published: true });
|
|
256
|
+
expect(await cache.publishedHead({ authorityId })).toEqual({
|
|
257
|
+
authorityId,
|
|
258
|
+
authoritySha256: firstAuthoritySha256,
|
|
259
|
+
generation: 1,
|
|
260
|
+
generationSha256: first.generationSha256,
|
|
261
|
+
membershipSha256: first.membershipSha256,
|
|
262
|
+
profileSha256: OH_CLOUDFLARE_EMBEDDING_PROFILE_V1.profileSha256,
|
|
263
|
+
publishedAt: instant1,
|
|
264
|
+
rendererSha256: expect.stringMatching(/^[a-f0-9]{64}$/u),
|
|
265
|
+
v: 1,
|
|
266
|
+
});
|
|
238
267
|
expect(await cache.publish({
|
|
239
268
|
authorityId,
|
|
240
269
|
expectedPublishedGeneration: null,
|
|
@@ -302,6 +331,12 @@ describe("libSQL derived semantic cache", () => {
|
|
|
302
331
|
generation: 2,
|
|
303
332
|
publishedAt: instant2,
|
|
304
333
|
})).toMatchObject({ published: true });
|
|
334
|
+
expect(await cache.publishedHead({ authorityId })).toMatchObject({
|
|
335
|
+
authoritySha256: secondAuthoritySha256,
|
|
336
|
+
generation: 2,
|
|
337
|
+
publishedAt: instant2,
|
|
338
|
+
v: 1,
|
|
339
|
+
});
|
|
305
340
|
expect(await cache.search({
|
|
306
341
|
authority: authority(authorityId, 1, firstAuthoritySha256, firstDocuments),
|
|
307
342
|
embeddingClient: embedder,
|
|
@@ -361,6 +396,9 @@ describe("libSQL derived semantic cache", () => {
|
|
|
361
396
|
embeddingClient: embedder,
|
|
362
397
|
query: "needle-alpha",
|
|
363
398
|
})).toEqual([]);
|
|
399
|
+
expect(await cache.publishedHead({
|
|
400
|
+
authorityId: "agent:session-a:epoch-1",
|
|
401
|
+
})).toBeNull();
|
|
364
402
|
|
|
365
403
|
expect((await cache.purgeAuthority({
|
|
366
404
|
authorityId: "agent:session-b:epoch-1",
|
|
@@ -472,6 +510,75 @@ describe("libSQL derived semantic cache", () => {
|
|
|
472
510
|
embeddingClient: embedder,
|
|
473
511
|
query: "needle-alpha",
|
|
474
512
|
})).rejects.toBeInstanceOf(OhLibSqlSemanticError);
|
|
513
|
+
await expect(cache.publishedHead({ authorityId: "INVALID" }))
|
|
514
|
+
.rejects.toMatchObject({ code: "invalid-input" });
|
|
515
|
+
await cache.close();
|
|
516
|
+
client.close();
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
test("rejects a published pointer that diverges from its immutable generation", async () => {
|
|
520
|
+
const client = await bootstrapped();
|
|
521
|
+
const cache = await openOhLibSqlSemanticCacheV1(client);
|
|
522
|
+
const authorityId = "agent:published-head-integrity:epoch-1";
|
|
523
|
+
await cache.stage({
|
|
524
|
+
authorityId,
|
|
525
|
+
authoritySha256: digest("published-head-integrity"),
|
|
526
|
+
createdAt: instant1,
|
|
527
|
+
documents: [document("memory:one", "needle-alpha")],
|
|
528
|
+
embeddingClient: embeddingClient([]),
|
|
529
|
+
generation: 1,
|
|
530
|
+
});
|
|
531
|
+
await cache.publish({
|
|
532
|
+
authorityId,
|
|
533
|
+
expectedPublishedGeneration: null,
|
|
534
|
+
generation: 1,
|
|
535
|
+
publishedAt: instant1,
|
|
536
|
+
});
|
|
537
|
+
client.database.query(`UPDATE oh_semantic_heads SET authority_sha256 = ?
|
|
538
|
+
WHERE authority_id = ?`).run(digest("forged-head"), authorityId);
|
|
539
|
+
await expect(cache.publishedHead({ authorityId }))
|
|
540
|
+
.rejects.toMatchObject({ code: "integrity" });
|
|
541
|
+
await cache.close();
|
|
542
|
+
client.close();
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
test("does not return a head that changed during its bounded read", async () => {
|
|
546
|
+
const client = new PublishedHeadInterleavingLibSqlClient();
|
|
547
|
+
await bootstrapOhLibSqlSemanticCacheV1(client, { appliedAt: instant1 });
|
|
548
|
+
const cache = await openOhLibSqlSemanticCacheV1(client);
|
|
549
|
+
const authorityId = "agent:published-head-race:epoch-1";
|
|
550
|
+
const embedder = embeddingClient([]);
|
|
551
|
+
for (const generation of [1, 2] as const) {
|
|
552
|
+
await cache.stage({
|
|
553
|
+
authorityId,
|
|
554
|
+
authoritySha256: digest(`published-head-race:${generation}`),
|
|
555
|
+
createdAt: generation === 1 ? instant1 : instant2,
|
|
556
|
+
documents: [document(
|
|
557
|
+
`memory:${generation}`,
|
|
558
|
+
generation === 1 ? "needle-alpha" : "needle-beta",
|
|
559
|
+
)],
|
|
560
|
+
embeddingClient: embedder,
|
|
561
|
+
generation,
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
await cache.publish({
|
|
565
|
+
authorityId,
|
|
566
|
+
expectedPublishedGeneration: null,
|
|
567
|
+
generation: 1,
|
|
568
|
+
publishedAt: instant1,
|
|
569
|
+
});
|
|
570
|
+
client.afterHeadRead = async () => {
|
|
571
|
+
await cache.publish({
|
|
572
|
+
authorityId,
|
|
573
|
+
expectedPublishedGeneration: 1,
|
|
574
|
+
generation: 2,
|
|
575
|
+
publishedAt: instant2,
|
|
576
|
+
});
|
|
577
|
+
};
|
|
578
|
+
await expect(cache.publishedHead({ authorityId }))
|
|
579
|
+
.rejects.toMatchObject({ code: "conflict" });
|
|
580
|
+
expect(await cache.publishedHead({ authorityId }))
|
|
581
|
+
.toMatchObject({ generation: 2, publishedAt: instant2 });
|
|
475
582
|
await cache.close();
|
|
476
583
|
client.close();
|
|
477
584
|
});
|
package/src/libsql-semantic.ts
CHANGED
|
@@ -79,6 +79,19 @@ export type OhSemanticPublishResultV1 = Readonly<{
|
|
|
79
79
|
v: 1;
|
|
80
80
|
}>;
|
|
81
81
|
|
|
82
|
+
/** The exact, currently published cache pointer for one semantic authority. */
|
|
83
|
+
export type OhSemanticPublishedHeadV1 = Readonly<{
|
|
84
|
+
authorityId: string;
|
|
85
|
+
authoritySha256: Sha256Hex;
|
|
86
|
+
generation: number;
|
|
87
|
+
generationSha256: Sha256Hex;
|
|
88
|
+
membershipSha256: Sha256Hex;
|
|
89
|
+
profileSha256: Sha256Hex;
|
|
90
|
+
publishedAt: string;
|
|
91
|
+
rendererSha256: Sha256Hex;
|
|
92
|
+
v: 1;
|
|
93
|
+
}>;
|
|
94
|
+
|
|
82
95
|
export type OhSemanticSearchResultV1 = Readonly<{
|
|
83
96
|
chunkOrdinal: number;
|
|
84
97
|
key: string;
|
|
@@ -749,6 +762,44 @@ export class OhLibSqlSemanticCacheV1 {
|
|
|
749
762
|
if (this.#closeClient) this.#client.close?.();
|
|
750
763
|
}
|
|
751
764
|
|
|
765
|
+
/**
|
|
766
|
+
* Reads the current compare-and-swap base without exposing cache rows or
|
|
767
|
+
* private source text. An absent or purged authority has no published head.
|
|
768
|
+
*/
|
|
769
|
+
async publishedHead(input: Readonly<{
|
|
770
|
+
authorityId: string;
|
|
771
|
+
}>): Promise<OhSemanticPublishedHeadV1 | null> {
|
|
772
|
+
this.#open();
|
|
773
|
+
const authorityId = parseAuthorityId(input.authorityId);
|
|
774
|
+
if (await readPurge(this.#client, authorityId) !== null) return null;
|
|
775
|
+
const head = await readHead(this.#client, authorityId);
|
|
776
|
+
if (head === null) return null;
|
|
777
|
+
const generation = await readGeneration(this.#client, authorityId, head.generation);
|
|
778
|
+
if (generation === null || !headMatchesGeneration(head, generation)) {
|
|
779
|
+
if (await readPurge(this.#client, authorityId) !== null) return null;
|
|
780
|
+
throw new OhLibSqlSemanticError(
|
|
781
|
+
"integrity",
|
|
782
|
+
"The semantic published head does not match its immutable generation.",
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
if (await readPurge(this.#client, authorityId) !== null) return null;
|
|
786
|
+
const finalHead = await readHead(this.#client, authorityId);
|
|
787
|
+
if (finalHead === null) {
|
|
788
|
+
if (await readPurge(this.#client, authorityId) !== null) return null;
|
|
789
|
+
throw new OhLibSqlSemanticError(
|
|
790
|
+
"integrity",
|
|
791
|
+
"The semantic published head disappeared during its read.",
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
if (canonicalJson(finalHead) !== canonicalJson(head)) {
|
|
795
|
+
throw new OhLibSqlSemanticError(
|
|
796
|
+
"conflict",
|
|
797
|
+
"The semantic published head changed during its read.",
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
return Object.freeze({ ...head, v: 1 });
|
|
801
|
+
}
|
|
802
|
+
|
|
752
803
|
async stage(input: Readonly<{
|
|
753
804
|
authorityId: string;
|
|
754
805
|
authoritySha256: Sha256Hex;
|