@melite/estate-drafts 0.0.0-stage → 0.1.5-pre-alpha
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/LICENSE +557 -0
- package/README.md +55 -2
- package/lib/bench.d.ts +376 -0
- package/lib/bench.js +1941 -0
- package/lib/index.d.ts +15 -0
- package/lib/index.js +17 -0
- package/lib/remote.d.ts +325 -0
- package/lib/remote.js +1307 -0
- package/lib/spec.d.ts +88 -0
- package/lib/spec.js +87 -0
- package/lib/store.d.ts +46 -0
- package/lib/store.js +194 -0
- package/lib/trail.d.ts +16 -0
- package/lib/trail.js +91 -0
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +1016 -0
- package/lib/typert.remote-client.d.ts +54 -0
- package/lib/typert.remote-client.js +732 -0
- package/lib/types.d.ts +3 -0
- package/lib/types.js +2 -0
- package/package.json +65 -4
package/lib/spec.d.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/** One drafts-domain record: the doc plus the recency column the
|
|
3
|
+
* list arm orders by (the file-mtime equivalent — seconds). */
|
|
4
|
+
declare const DraftRecordSchema: z.ZodObject<{
|
|
5
|
+
updated_at: z.ZodNumber;
|
|
6
|
+
doc: z.ZodObject<{
|
|
7
|
+
draft_id: z.ZodString;
|
|
8
|
+
status: z.ZodString;
|
|
9
|
+
createdAt: z.ZodNumber;
|
|
10
|
+
completedAt: z.ZodUnion<readonly [z.ZodNumber, z.ZodNull]>;
|
|
11
|
+
intent: z.ZodString;
|
|
12
|
+
stages: z.ZodArray<z.ZodObject<{
|
|
13
|
+
id: z.ZodString;
|
|
14
|
+
}, z.core.$loose>>;
|
|
15
|
+
}, z.core.$loose>;
|
|
16
|
+
}, z.core.$strip>;
|
|
17
|
+
export type DraftRecord = z.infer<typeof DraftRecordSchema>;
|
|
18
|
+
/** One character record (audit 004/C2): the reusable identity
|
|
19
|
+
* bundle. `references` is the ORDERED reference-image list — path
|
|
20
|
+
* strings of exactly the class the submit walk stages (data URLs,
|
|
21
|
+
* http(s), estate asset URLs, runs-relative and cwd-relative
|
|
22
|
+
* paths; `uploadReferenced` walks the whole graph and stages any
|
|
23
|
+
* of them, audit 004 §3), order preserved — sheet views are
|
|
24
|
+
* position-coded. `description` is the LOCKED identity prompt (a
|
|
25
|
+
* character-sheet ten-section block, audit 004/C7's skill); the
|
|
26
|
+
* run-time seam (how it reaches a prompt) is C3's decision, not
|
|
27
|
+
* this table's. */
|
|
28
|
+
declare const CharacterRecordSchema: z.ZodObject<{
|
|
29
|
+
name: z.ZodString;
|
|
30
|
+
references: z.ZodArray<z.ZodString>;
|
|
31
|
+
description: z.ZodString;
|
|
32
|
+
updated_at: z.ZodNumber;
|
|
33
|
+
}, z.core.$strip>;
|
|
34
|
+
export type CharacterRecord = z.infer<typeof CharacterRecordSchema>;
|
|
35
|
+
/** The draft-id law (editor_runs.818-822 heritage): 1..96 chars,
|
|
36
|
+
* [A-Za-z0-9_-] ASCII only, path-safe. THE SSOT — the queue's
|
|
37
|
+
* submit gate and the drafts remote both test this one address
|
|
38
|
+
* (the queue re-exports it; gateway-core's copy is dead). */
|
|
39
|
+
export declare const DRAFT_ID_RE: RegExp;
|
|
40
|
+
/** The id's PATTERN and its bound, published so every gate that
|
|
41
|
+
* admits a draft id enforces the SAME rule (the census's H13: the
|
|
42
|
+
* queue's copy had no cap, so a long id passed one door and was
|
|
43
|
+
* refused by the next with a different voice). */
|
|
44
|
+
export declare const DRAFT_ID_PATTERN: RegExp;
|
|
45
|
+
export declare const DRAFT_ID_MAX_LEN = 96;
|
|
46
|
+
/** draft_id_ok (the doors' hand-loop drop-in): the law, one voice. */
|
|
47
|
+
export declare function draftIdOk(draftId: string): boolean;
|
|
48
|
+
/** The drafts domain (unit dir `drafts/`, version 2 — the
|
|
49
|
+
* characters table join; version-1 record documents stay
|
|
50
|
+
* readable through compatibleVersions, the per-record
|
|
51
|
+
* contract's accepted-stamp set). */
|
|
52
|
+
export declare const draftsDomainSpec: {
|
|
53
|
+
name: string;
|
|
54
|
+
version: number;
|
|
55
|
+
compatibleVersions: number[];
|
|
56
|
+
layout: "per-record";
|
|
57
|
+
invalidRecords: "backup-and-skip";
|
|
58
|
+
global: {
|
|
59
|
+
schema: z.ZodNumber;
|
|
60
|
+
initial: number;
|
|
61
|
+
};
|
|
62
|
+
tables: {
|
|
63
|
+
docs: import("@deepseek-ai/dsh-storage-domain").DomainTableSpec<string, {
|
|
64
|
+
updated_at: number;
|
|
65
|
+
doc: {
|
|
66
|
+
[x: string]: unknown;
|
|
67
|
+
draft_id: string;
|
|
68
|
+
status: string;
|
|
69
|
+
createdAt: number;
|
|
70
|
+
completedAt: number | null;
|
|
71
|
+
intent: string;
|
|
72
|
+
stages: {
|
|
73
|
+
[x: string]: unknown;
|
|
74
|
+
id: string;
|
|
75
|
+
}[];
|
|
76
|
+
};
|
|
77
|
+
}>;
|
|
78
|
+
characters: import("@deepseek-ai/dsh-storage-domain").DomainTableSpec<string, {
|
|
79
|
+
name: string;
|
|
80
|
+
references: string[];
|
|
81
|
+
description: string;
|
|
82
|
+
updated_at: number;
|
|
83
|
+
}>;
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
export type DraftsDomainSpec = typeof draftsDomainSpec;
|
|
87
|
+
export {};
|
|
88
|
+
//# sourceMappingURL=spec.d.ts.map
|
package/lib/spec.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
|
|
2
|
+
//
|
|
3
|
+
// The domain truth for draft docs: table `docs` keyed by draft id
|
|
4
|
+
// (DRAFT_ID_RE admits path-safe chars only, so the identity codec
|
|
5
|
+
// holds — no b64u here), one record per draft (large, sparse,
|
|
6
|
+
// individually disposable), schema-failing records backed up and
|
|
7
|
+
// skipped by the facility (`invalidRecords: 'backup-and-skip'`).
|
|
8
|
+
// The global slot is the one-time legacy-import marker: 0 until the
|
|
9
|
+
// legacy drafts tree has been imported, the import epoch (ms) after.
|
|
10
|
+
//
|
|
11
|
+
|
|
12
|
+
// character bundle (ordered reference paths + a locked description),
|
|
13
|
+
// same id grammar as drafts (one address class, one law — the
|
|
14
|
+
// per-record layout's SAFE_KEY_RE is exactly DRAFT_ID_RE's set), the
|
|
15
|
+
// storage root itself is the tenant boundary. Version 2 with
|
|
16
|
+
// compatibleVersions [1]: existing v1-stamped record documents keep
|
|
17
|
+
// reading green (the per-record contract accepts the declared set),
|
|
18
|
+
// new writes stamp 2.
|
|
19
|
+
import { defineDomain, domainTable } from "@deepseek-ai/dsh-storage-domain";
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
/** One draft doc — the composer's shape: six contract keys plus
|
|
22
|
+
* caller extras verbatim. THE SERVE READ IS DELIBERATELY LOOSE
|
|
23
|
+
* (the write doors are strict; this read hands the composer's own
|
|
24
|
+
* extras back, pinned by bench-coverage's "extras ride the loose
|
|
25
|
+
* schema" row) — a looseObject needs no .passthrough() on top. */
|
|
26
|
+
const DraftDocSchema = z.looseObject({
|
|
27
|
+
draft_id: z.string(),
|
|
28
|
+
status: z.string(),
|
|
29
|
+
createdAt: z.number(),
|
|
30
|
+
completedAt: z.union([z.number(), z.null()]),
|
|
31
|
+
intent: z.string(),
|
|
32
|
+
stages: z.array(z.looseObject({ id: z.string() })),
|
|
33
|
+
});
|
|
34
|
+
/** One drafts-domain record: the doc plus the recency column the
|
|
35
|
+
* list arm orders by (the file-mtime equivalent — seconds). */
|
|
36
|
+
const DraftRecordSchema = z.object({
|
|
37
|
+
updated_at: z.number().int(),
|
|
38
|
+
doc: DraftDocSchema,
|
|
39
|
+
});
|
|
40
|
+
/** One character record (audit 004/C2): the reusable identity
|
|
41
|
+
* bundle. `references` is the ORDERED reference-image list — path
|
|
42
|
+
* strings of exactly the class the submit walk stages (data URLs,
|
|
43
|
+
* http(s), estate asset URLs, runs-relative and cwd-relative
|
|
44
|
+
* paths; `uploadReferenced` walks the whole graph and stages any
|
|
45
|
+
* of them, audit 004 §3), order preserved — sheet views are
|
|
46
|
+
* position-coded. `description` is the LOCKED identity prompt (a
|
|
47
|
+
* character-sheet ten-section block, audit 004/C7's skill); the
|
|
48
|
+
* run-time seam (how it reaches a prompt) is C3's decision, not
|
|
49
|
+
* this table's. */
|
|
50
|
+
const CharacterRecordSchema = z.object({
|
|
51
|
+
name: z.string().min(1),
|
|
52
|
+
references: z.array(z.string()),
|
|
53
|
+
description: z.string(),
|
|
54
|
+
updated_at: z.number().int(),
|
|
55
|
+
});
|
|
56
|
+
/** The draft-id law (editor_runs.818-822 heritage): 1..96 chars,
|
|
57
|
+
* [A-Za-z0-9_-] ASCII only, path-safe. THE SSOT — the queue's
|
|
58
|
+
* submit gate and the drafts remote both test this one address
|
|
59
|
+
* (the queue re-exports it; gateway-core's copy is dead). */
|
|
60
|
+
export const DRAFT_ID_RE = /^[A-Za-z0-9_-]{1,96}$/;
|
|
61
|
+
/** The id's PATTERN and its bound, published so every gate that
|
|
62
|
+
* admits a draft id enforces the SAME rule (the census's H13: the
|
|
63
|
+
* queue's copy had no cap, so a long id passed one door and was
|
|
64
|
+
* refused by the next with a different voice). */
|
|
65
|
+
export const DRAFT_ID_PATTERN = DRAFT_ID_RE;
|
|
66
|
+
export const DRAFT_ID_MAX_LEN = 96;
|
|
67
|
+
/** draft_id_ok (the doors' hand-loop drop-in): the law, one voice. */
|
|
68
|
+
export function draftIdOk(draftId) {
|
|
69
|
+
return DRAFT_ID_RE.test(draftId);
|
|
70
|
+
}
|
|
71
|
+
/** The drafts domain (unit dir `drafts/`, version 2 — the
|
|
72
|
+
* characters table join; version-1 record documents stay
|
|
73
|
+
* readable through compatibleVersions, the per-record
|
|
74
|
+
* contract's accepted-stamp set). */
|
|
75
|
+
export const draftsDomainSpec = defineDomain({
|
|
76
|
+
name: "drafts",
|
|
77
|
+
version: 2,
|
|
78
|
+
compatibleVersions: [1],
|
|
79
|
+
layout: "per-record",
|
|
80
|
+
invalidRecords: "backup-and-skip",
|
|
81
|
+
global: { schema: z.number().int(), initial: 0 },
|
|
82
|
+
tables: {
|
|
83
|
+
docs: domainTable(DraftRecordSchema),
|
|
84
|
+
characters: domainTable(CharacterRecordSchema),
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
//# sourceMappingURL=spec.js.map
|
package/lib/store.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Service, type Context } from "@deepseek-ai/cordis";
|
|
2
|
+
import type { Domain } from "@deepseek-ai/dsh-storage-domain";
|
|
3
|
+
import type { DraftsDomainSpec } from "./spec.js";
|
|
4
|
+
/** The drafts plugin's config (J12: the retired
|
|
5
|
+
* MELITE_STORAGE_ROOT / MELITE_STATE_ROOT env seams' config
|
|
6
|
+
* twin — the unit-file root the json backend hangs under,
|
|
7
|
+
* named at the composition site, never read from the env). */
|
|
8
|
+
export interface DraftsConfig {
|
|
9
|
+
/** The storage unit-file root (`<stateRoot>/storage` at the
|
|
10
|
+
* composition site); default `data/runs/storage`. */
|
|
11
|
+
storageRoot?: string;
|
|
12
|
+
}
|
|
13
|
+
/** The json backend's unit-file root. */
|
|
14
|
+
export declare function resolveStorageRoot(config?: Readonly<DraftsConfig>): string;
|
|
15
|
+
/** The fiber-scoped seat owning the drafts domain handles (id
|
|
16
|
+
* `meliteDraftsDomain` — the estate-seat prefix law, cf.
|
|
17
|
+
* skillRegistry.ts:20-28): one instance per mounting fiber, so the
|
|
18
|
+
* cached opens die with the fiber instead of living at module
|
|
19
|
+
* scope. Fields are TS-`private`, never `#private` — cordis
|
|
20
|
+
* `ctx.get` returns a proxy and private class fields die through
|
|
21
|
+
* it ("Cannot read private member ... whose class did not declare
|
|
22
|
+
* it"; the flows seat, flowsData.ts, is the measured precedent). */
|
|
23
|
+
export declare class DraftsDomainSeatService extends Service {
|
|
24
|
+
static readonly inject: {
|
|
25
|
+
readonly optional: readonly [];
|
|
26
|
+
};
|
|
27
|
+
/** Per-facility open handles, keyed by the facility object (one
|
|
28
|
+
* slot per provider; GC-bounded, fiber-lifetime). */
|
|
29
|
+
private cached;
|
|
30
|
+
constructor(ctx: Context, config?: Readonly<DraftsConfig>);
|
|
31
|
+
/** The composition site's named storage root (J12: config —
|
|
32
|
+
* the retired env seam's twin). */
|
|
33
|
+
readonly config: Readonly<DraftsConfig>;
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
open(): Promise<Domain<DraftsDomainSpec>>;
|
|
39
|
+
}
|
|
40
|
+
/** Open (or reuse) the drafts domain through the mounted seat. The
|
|
41
|
+
* apply mounts `DraftsDomainSeatService` before the remote
|
|
42
|
+
* services; tests mount it on their own Context.
|
|
43
|
+
* @param ctx - the Context whose fiber carries the seat.
|
|
44
|
+
* @returns the ready domain handle. */
|
|
45
|
+
export declare function openDraftsDomain(ctx: Context): Promise<Domain<DraftsDomainSpec>>;
|
|
46
|
+
//# sourceMappingURL=store.d.ts.map
|
package/lib/store.js
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
// store.ts — open the drafts domain through the host's facility.
|
|
2
|
+
//
|
|
3
|
+
// The facility seat (`ctx.storageDomain`) is provided by the HOST's
|
|
4
|
+
// storage rows (json backend + domain facility); this package never
|
|
5
|
+
// bundles them. The open is lazy and process-scoped (one open per
|
|
6
|
+
// storage root — the domain write chain serializes concurrent puts,
|
|
7
|
+
// so sharing the handle is correct), cached on first use.
|
|
8
|
+
//
|
|
9
|
+
// First open runs the one-time legacy import (the pre-domain
|
|
10
|
+
// STATE_ROOT/drafts tree → the domain), guarded by the global slot:
|
|
11
|
+
// runs only while it serves the initial 0, then stamps the epoch —
|
|
12
|
+
// a wiped unit never re-imports frozen ghosts. The legacy files stay
|
|
13
|
+
// untouched on disk — evidence, never truth.
|
|
14
|
+
import { Service } from "@deepseek-ai/cordis";
|
|
15
|
+
import { readFileSync, readdirSync, statSync } from "node:fs";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
import { isPurelyUnstatedRequiredVoice } from "@melite/contracts/laneRelations";
|
|
18
|
+
import { draftsDomainSpec } from "./spec.js";
|
|
19
|
+
/** The json backend's unit-file root. */
|
|
20
|
+
export function resolveStorageRoot(config = {}) {
|
|
21
|
+
return config.storageRoot ?? join("data", "runs", "storage");
|
|
22
|
+
}
|
|
23
|
+
/** The one-time legacy-drafts import (the pre-domain drafts tree →
|
|
24
|
+
* the drafts domain). Unparseable/non-object legacy files skip;
|
|
25
|
+
* schema-failing rows skip individually; the rest import. */
|
|
26
|
+
async function importLegacyDrafts(drafts, storageRoot) {
|
|
27
|
+
const marker = drafts.global;
|
|
28
|
+
if (marker.get() !== 0)
|
|
29
|
+
return;
|
|
30
|
+
const docs = drafts.table("docs");
|
|
31
|
+
const legacyDir = join(storageRoot, "..", "drafts");
|
|
32
|
+
let names;
|
|
33
|
+
try {
|
|
34
|
+
names = readdirSync(legacyDir);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
names = [];
|
|
38
|
+
}
|
|
39
|
+
for (const name of names) {
|
|
40
|
+
if (!name.endsWith(".json"))
|
|
41
|
+
continue;
|
|
42
|
+
const did = name.slice(0, -5);
|
|
43
|
+
if (docs.get(did) !== undefined)
|
|
44
|
+
continue;
|
|
45
|
+
let doc;
|
|
46
|
+
try {
|
|
47
|
+
doc = JSON.parse(readFileSync(join(legacyDir, name), "utf8"));
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (typeof doc !== "object" || doc === null || Array.isArray(doc))
|
|
53
|
+
continue;
|
|
54
|
+
const rec = doc;
|
|
55
|
+
if (typeof rec["draft_id"] !== "string")
|
|
56
|
+
rec["draft_id"] = did;
|
|
57
|
+
let updated = Math.floor(Date.now() / 1000);
|
|
58
|
+
try {
|
|
59
|
+
updated = Math.floor(statSync(join(legacyDir, name)).mtimeMs / 1000);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
/* unstatable imports as now — ordering stays total */
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
await docs.put(did, {
|
|
66
|
+
updated_at: updated,
|
|
67
|
+
doc: rec,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
/* a schema-failing legacy row skips; the rest import */
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
await marker.set(Date.now());
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
async function healReplayedRefusals(drafts) {
|
|
89
|
+
const docs = drafts.table("docs");
|
|
90
|
+
for (const [did, rec] of docs.entries()) {
|
|
91
|
+
// persisted rows are a durable boundary: a record without a doc
|
|
92
|
+
// object (or without stages) carries no bench row to heal
|
|
93
|
+
const doc = typeof rec === "object" && rec !== null ? rec.doc : undefined;
|
|
94
|
+
if (typeof doc !== "object" || doc === null)
|
|
95
|
+
continue;
|
|
96
|
+
const stages = Array.isArray(doc.stages) ? doc.stages : [];
|
|
97
|
+
let changed = false;
|
|
98
|
+
for (const stage of stages) {
|
|
99
|
+
if (typeof stage !== "object" || stage === null)
|
|
100
|
+
continue;
|
|
101
|
+
const row = stage;
|
|
102
|
+
const runStatus = row["runStatus"];
|
|
103
|
+
if (runStatus !== "failed" && runStatus !== "error")
|
|
104
|
+
continue;
|
|
105
|
+
const result = row["result"];
|
|
106
|
+
const error = typeof result === "object" && result !== null
|
|
107
|
+
? result["error"]
|
|
108
|
+
: undefined;
|
|
109
|
+
// PURELY the class (the mixed-carry guard): a composite
|
|
110
|
+
// refusal can join the stem with a stated-wrong half — that
|
|
111
|
+
// row keeps its failed face, only whole-stem texts heal
|
|
112
|
+
if (typeof error !== "string" || !isPurelyUnstatedRequiredVoice(error)) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
row["runStatus"] = "";
|
|
116
|
+
row["result"] = {};
|
|
117
|
+
changed = true;
|
|
118
|
+
}
|
|
119
|
+
if (changed)
|
|
120
|
+
await docs.put(did, rec);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/** The fiber-scoped seat owning the drafts domain handles (id
|
|
124
|
+
* `meliteDraftsDomain` — the estate-seat prefix law, cf.
|
|
125
|
+
* skillRegistry.ts:20-28): one instance per mounting fiber, so the
|
|
126
|
+
* cached opens die with the fiber instead of living at module
|
|
127
|
+
* scope. Fields are TS-`private`, never `#private` — cordis
|
|
128
|
+
* `ctx.get` returns a proxy and private class fields die through
|
|
129
|
+
* it ("Cannot read private member ... whose class did not declare
|
|
130
|
+
* it"; the flows seat, flowsData.ts, is the measured precedent). */
|
|
131
|
+
export class DraftsDomainSeatService extends Service {
|
|
132
|
+
static inject = { optional: [] };
|
|
133
|
+
/** Per-facility open handles, keyed by the facility object (one
|
|
134
|
+
* slot per provider; GC-bounded, fiber-lifetime). */
|
|
135
|
+
cached = new WeakMap();
|
|
136
|
+
constructor(ctx, config = {}) {
|
|
137
|
+
// the estate-seat id: dsh core may own the bare name
|
|
138
|
+
super(ctx, "meliteDraftsDomain");
|
|
139
|
+
this.config = config;
|
|
140
|
+
}
|
|
141
|
+
/** The composition site's named storage root (J12: config —
|
|
142
|
+
* the retired env seam's twin). */
|
|
143
|
+
config;
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
async open() {
|
|
149
|
+
const root = resolveStorageRoot(this.config);
|
|
150
|
+
// OPTIONAL-dependency probe per the tutorial's documented pattern
|
|
151
|
+
// (cordis-tutorial/03-services.md: "skip inject and probe at the
|
|
152
|
+
// use site" — property reads demand inject on loader-mounted
|
|
153
|
+
// fibers; ctx.get returns the active provider or undefined).
|
|
154
|
+
const facility = this.ctx.get("storageDomain");
|
|
155
|
+
if (facility === undefined || facility === null) {
|
|
156
|
+
throw new Error("[estate-drafts] storage domain facility not mounted (ctx.storageDomain)");
|
|
157
|
+
}
|
|
158
|
+
const hit = this.cached.get(facility);
|
|
159
|
+
// (J12) the config root is immutable per boot, so a cached hit is
|
|
160
|
+
// always this root — the drifting-mid-process root the retired
|
|
161
|
+
|
|
162
|
+
if (hit !== undefined)
|
|
163
|
+
return hit.ready;
|
|
164
|
+
const ready = (async () => {
|
|
165
|
+
const drafts = await facility.open(draftsDomainSpec);
|
|
166
|
+
await importLegacyDrafts(drafts, root);
|
|
167
|
+
await healReplayedRefusals(drafts);
|
|
168
|
+
return drafts;
|
|
169
|
+
})();
|
|
170
|
+
this.cached.set(facility, { root, ready });
|
|
171
|
+
try {
|
|
172
|
+
return await ready;
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
// Unconditional: one slot per facility exists at a time (a
|
|
176
|
+
// foreign root refuses above), so this rejection owns the slot.
|
|
177
|
+
this.cached.delete(facility);
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/** Open (or reuse) the drafts domain through the mounted seat. The
|
|
183
|
+
* apply mounts `DraftsDomainSeatService` before the remote
|
|
184
|
+
* services; tests mount it on their own Context.
|
|
185
|
+
* @param ctx - the Context whose fiber carries the seat.
|
|
186
|
+
* @returns the ready domain handle. */
|
|
187
|
+
export async function openDraftsDomain(ctx) {
|
|
188
|
+
const seat = ctx.get("meliteDraftsDomain");
|
|
189
|
+
if (seat === undefined || seat === null) {
|
|
190
|
+
throw new Error("[estate-drafts] drafts domain seat not mounted (new DraftsDomainSeatService(ctx))");
|
|
191
|
+
}
|
|
192
|
+
return seat.open();
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=store.js.map
|
package/lib/trail.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare function canonicalOutputUrl(url: string): string;
|
|
2
|
+
/** A result's OUTPUT urls, one spelling: the artifacts name→url map
|
|
3
|
+
* first, the flat assetUrls list second, the lone url third (the
|
|
4
|
+
* same harvest order the client's preview reads). */
|
|
5
|
+
export declare function resultOutputUrls(result: unknown): string[];
|
|
6
|
+
/** The draft row's terminal words (see remote.ts's trail laws). */
|
|
7
|
+
export declare function isTerminalTrailWord(v: unknown): boolean;
|
|
8
|
+
/** Same run identity: two non-empty equal run_id strings. */
|
|
9
|
+
export declare function sameTrailRunIdentity(a: unknown, b: unknown): boolean;
|
|
10
|
+
/** THE ONE-ROW-PER-RUN TRAIL (bench-trail W2's law, applied to any
|
|
11
|
+
* carried history): identity is the run_id first — the media route
|
|
12
|
+
* and the gallery proxy spell the same file differently — with the
|
|
13
|
+
* canonical-url set as the no-id fallback. Order preserved, newest
|
|
14
|
+
* first in, newest first out. */
|
|
15
|
+
export declare function trailDedupe(history: unknown[]): unknown[];
|
|
16
|
+
//# sourceMappingURL=trail.d.ts.map
|
package/lib/trail.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
|
|
2
|
+
// trail's dedup/canonical laws live here so EVERY carrier obeys them
|
|
3
|
+
// — the patch door's archive (remote.ts), the load/undo memory carry
|
|
4
|
+
// (bench.ts), and any future writer. remote.ts imports bench.ts, so
|
|
5
|
+
// bench-facing carriers can never import remote.ts back; this module
|
|
6
|
+
// is the cycle-free home both sides share.
|
|
7
|
+
import { RUN_MEDIA_PREFIX } from "@melite/contracts/benchStage";
|
|
8
|
+
/** One url, canonical: the run-relative path. The media route and
|
|
9
|
+
* the gallery proxy spell the SAME file two ways
|
|
10
|
+
* (/melite-media/runs/<rest>, /infer-gallery/artifact?path=<enc>) —
|
|
11
|
+
* every dedup compares canonical forms, never wire spellings. The
|
|
12
|
+
* run-media prefix rides the contract's constant — the closure
|
|
13
|
+
* gate's no-second-copy law. */
|
|
14
|
+
const MEDIA_RUNS = RUN_MEDIA_PREFIX + "/";
|
|
15
|
+
export function canonicalOutputUrl(url) {
|
|
16
|
+
if (url.startsWith(MEDIA_RUNS))
|
|
17
|
+
return url.slice(MEDIA_RUNS.length);
|
|
18
|
+
if (url.startsWith("/infer-gallery/artifact?path=")) {
|
|
19
|
+
try {
|
|
20
|
+
return decodeURIComponent(url.slice("/infer-gallery/artifact?path=".length));
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// a malformed escape is not a spelling of another file — keep it
|
|
24
|
+
return url;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return url;
|
|
28
|
+
}
|
|
29
|
+
/** A result's OUTPUT urls, one spelling: the artifacts name→url map
|
|
30
|
+
* first, the flat assetUrls list second, the lone url third (the
|
|
31
|
+
* same harvest order the client's preview reads). */
|
|
32
|
+
export function resultOutputUrls(result) {
|
|
33
|
+
if (typeof result !== "object" || result === null || Array.isArray(result))
|
|
34
|
+
return [];
|
|
35
|
+
const row = result;
|
|
36
|
+
const urls = [];
|
|
37
|
+
if (row["artifacts"] !== null && typeof row["artifacts"] === "object" && !Array.isArray(row["artifacts"])) {
|
|
38
|
+
for (const v of Object.values(row["artifacts"])) {
|
|
39
|
+
if (typeof v === "string" && v !== "")
|
|
40
|
+
urls.push(v);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (urls.length === 0 && Array.isArray(row["assetUrls"])) {
|
|
44
|
+
for (const v of row["assetUrls"]) {
|
|
45
|
+
if (typeof v === "string" && v !== "")
|
|
46
|
+
urls.push(v);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (urls.length === 0 && typeof row["url"] === "string" && row["url"] !== "")
|
|
50
|
+
urls.push(row["url"]);
|
|
51
|
+
return urls;
|
|
52
|
+
}
|
|
53
|
+
const TRAIL_TERMINAL = new Set(["done", "completed", "failed", "stopped"]);
|
|
54
|
+
/** The draft row's terminal words (see remote.ts's trail laws). */
|
|
55
|
+
export function isTerminalTrailWord(v) {
|
|
56
|
+
return typeof v === "string" && TRAIL_TERMINAL.has(v);
|
|
57
|
+
}
|
|
58
|
+
/** Same run identity: two non-empty equal run_id strings. */
|
|
59
|
+
export function sameTrailRunIdentity(a, b) {
|
|
60
|
+
return typeof a === "string" && a !== "" && a === b;
|
|
61
|
+
}
|
|
62
|
+
/** THE ONE-ROW-PER-RUN TRAIL (bench-trail W2's law, applied to any
|
|
63
|
+
* carried history): identity is the run_id first — the media route
|
|
64
|
+
* and the gallery proxy spell the same file differently — with the
|
|
65
|
+
* canonical-url set as the no-id fallback. Order preserved, newest
|
|
66
|
+
* first in, newest first out. */
|
|
67
|
+
export function trailDedupe(history) {
|
|
68
|
+
const out = [];
|
|
69
|
+
const runIds = new Set();
|
|
70
|
+
const sets = [];
|
|
71
|
+
for (const entry of history) {
|
|
72
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
73
|
+
out.push(entry);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const row = entry;
|
|
77
|
+
const rid = row["run_id"];
|
|
78
|
+
if (typeof rid === "string" && rid !== "") {
|
|
79
|
+
if (runIds.has(rid))
|
|
80
|
+
continue;
|
|
81
|
+
runIds.add(rid);
|
|
82
|
+
}
|
|
83
|
+
const canon = new Set(resultOutputUrls(row).map(canonicalOutputUrl));
|
|
84
|
+
if (sets.some((s) => s.size === canon.size && [...s].every((u) => canon.has(u))))
|
|
85
|
+
continue;
|
|
86
|
+
sets.push(canon);
|
|
87
|
+
out.push(entry);
|
|
88
|
+
}
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=trail.js.map
|