mdkg 0.5.0 → 0.5.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/CHANGELOG.md +17 -0
- package/CLI_COMMAND_MATRIX.md +17 -5
- package/README.md +11 -1
- package/dist/cli.js +8 -3
- package/dist/command-contract.json +24 -16
- package/dist/commands/archive.js +176 -39
- package/dist/init/init-manifest.json +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,23 @@ mdkg is pre-v1 public alpha software. Command, graph, cache, bundle, and DAL con
|
|
|
8
8
|
|
|
9
9
|
## Unreleased
|
|
10
10
|
|
|
11
|
+
## 0.5.1 - 2026-07-14
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- Archive compression now derives writable ownership from enabled local
|
|
16
|
+
workspaces. `archive compress --all --ws <local-alias>` limits bulk selection,
|
|
17
|
+
and exact workspace-qualified archive qids are supported for direct targets.
|
|
18
|
+
- Archive compression JSON receipts preserve existing fields and add selected
|
|
19
|
+
workspace and read-only imported-projection exclusion evidence.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- Fixed `archive compress --all` traversing imported subgraph ZIP-fragment paths
|
|
24
|
+
and potentially mutating earlier local archives before a later invalid target
|
|
25
|
+
failed. Imported archives remain discoverable but fail closed for direct
|
|
26
|
+
compression, and the complete local selection is preflighted before writes.
|
|
27
|
+
|
|
11
28
|
## 0.5.0 - 2026-07-11
|
|
12
29
|
|
|
13
30
|
### Added
|
package/CLI_COMMAND_MATRIX.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# CLI Command Matrix
|
|
2
2
|
|
|
3
|
-
as_of: 2026-07-
|
|
4
|
-
package_version_in_source: 0.5.
|
|
3
|
+
as_of: 2026-07-14
|
|
4
|
+
package_version_in_source: 0.5.1
|
|
5
5
|
source: live help from `src/cli.ts`, runtime command handlers, and `dec-15`..`dec-18`
|
|
6
6
|
status: canonical single-source command and flag reference for mdkg
|
|
7
7
|
|
|
@@ -657,9 +657,9 @@ Usage:
|
|
|
657
657
|
- `mdkg archive show <id-or-archive-uri> [--ws <alias>] [--json]`
|
|
658
658
|
- `mdkg archive verify [id-or-archive-uri] [--json]`
|
|
659
659
|
- `mdkg archive verify [id-or-archive-uri] [--ws <alias>] [--json]`
|
|
660
|
-
- `mdkg archive compress <id-or-archive-uri|--all> [--json]`
|
|
661
|
-
- `mdkg archive compress <id-or-archive-uri> [--ws <alias>] [--json]`
|
|
662
|
-
- `mdkg archive compress --all [--json]`
|
|
660
|
+
- `mdkg archive compress <id-or-archive-uri-or-qid|--all> [--ws <local-alias>] [--json]`
|
|
661
|
+
- `mdkg archive compress <id-or-archive-uri-or-qid> [--ws <local-alias>] [--json]`
|
|
662
|
+
- `mdkg archive compress --all [--ws <local-alias>] [--json]`
|
|
663
663
|
|
|
664
664
|
Fields:
|
|
665
665
|
- archive sidecars use `type: archive`
|
|
@@ -673,6 +673,18 @@ Notes:
|
|
|
673
673
|
- archive visibility defaults to `private`
|
|
674
674
|
- `archive://<archive.id>` refs are validated against local archive sidecars
|
|
675
675
|
- `archive verify` and `validate` both check the ZIP hash, ZIP readability, payload SHA-256, and payload byte size
|
|
676
|
+
- archive compression is owned exclusively by enabled local workspaces; imported
|
|
677
|
+
archive projections remain visible to list/show/search but are read-only
|
|
678
|
+
- `archive compress --all` selects every locally writable archive, while
|
|
679
|
+
`--all --ws <local-alias>` limits selection to one enabled local workspace
|
|
680
|
+
- direct workspace-qualified qids such as `root:archive.example` are supported;
|
|
681
|
+
imported qids and `--ws <imported-alias>` fail before filesystem path
|
|
682
|
+
construction with source-workspace and bundle-refresh guidance
|
|
683
|
+
- compression preflights ownership, identity, containment, symlinks, raw input,
|
|
684
|
+
and destinations for the complete selected set before the first write
|
|
685
|
+
- JSON keeps `action`, `count`, and `archives`, and adds
|
|
686
|
+
`selection: { requested_workspace, selected_workspaces,
|
|
687
|
+
excluded_read_only: [{ workspace, qid, reason }] }`
|
|
676
688
|
- `archive verify` passes when the raw local source file is missing but the committed sidecar and ZIP cache are valid
|
|
677
689
|
- generated raw source copies live under `.mdkg/archive/**/source/` and are ignored by default
|
|
678
690
|
- `doctor` warns when archive ZIP caches exceed `archive.large_cache_warning_bytes` (default `26214400`; `0` disables)
|
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ mdkg stays deliberately boring:
|
|
|
14
14
|
- first-class rebuildable SQLite cache through built-in `node:sqlite`
|
|
15
15
|
- no daemon, hosted index, or vector DB
|
|
16
16
|
|
|
17
|
-
Current package version in source: `0.5.
|
|
17
|
+
Current package version in source: `0.5.1`
|
|
18
18
|
|
|
19
19
|
mdkg is still pre-v1 public alpha software. The public package is usable, but graph, cache, bundle, and DAL contracts may continue to change quickly while the project converges on a stable v1 surface.
|
|
20
20
|
|
|
@@ -653,6 +653,16 @@ When the source passed to `mdkg archive add` is inside the repo, `source_path` i
|
|
|
653
653
|
|
|
654
654
|
Archive sidecar visibility defaults to `private`. Use `mdkg archive add --visibility public` only when the sidecar metadata and ZIP cache are safe for public packs or public bundles.
|
|
655
655
|
|
|
656
|
+
Archive compression follows workspace ownership. `mdkg archive compress --all`
|
|
657
|
+
compresses archives from enabled local workspaces and reports read-only imported
|
|
658
|
+
archive projections as exclusions; it never treats bundle ZIP fragments as
|
|
659
|
+
filesystem paths. Use `--all --ws <local-alias>` to limit bulk compression or a
|
|
660
|
+
workspace-qualified qid such as `root:archive.example` for an exact local
|
|
661
|
+
target. Direct imported qids and imported `--ws` aliases fail before writes with
|
|
662
|
+
guidance to compress in the source workspace and refresh its subgraph bundle.
|
|
663
|
+
The JSON receipt preserves `action`, `count`, and `archives` and adds the
|
|
664
|
+
requested workspace, selected local workspaces, and excluded read-only qids.
|
|
665
|
+
|
|
656
666
|
By default, init/upgrade ignore generated raw archive source copies with `.mdkg/archive/**/source/`; sidecar `.md` files and compressed `.zip` caches remain commit-eligible. `mdkg doctor` warns when a committed archive ZIP cache exceeds `archive.large_cache_warning_bytes` in `.mdkg/config.json` (default `26214400`; set `0` to disable). Large-cache warnings do not block archive add or validation.
|
|
657
667
|
|
|
658
668
|
## Current direction
|
package/dist/cli.js
CHANGED
|
@@ -583,8 +583,13 @@ function printArchiveHelp(log, subcommand) {
|
|
|
583
583
|
break;
|
|
584
584
|
case "compress":
|
|
585
585
|
log("Usage:");
|
|
586
|
-
log(" mdkg archive compress <id-or-archive-uri> [--ws <alias>] [--json]");
|
|
587
|
-
log(" mdkg archive compress --all [--json]");
|
|
586
|
+
log(" mdkg archive compress <id-or-archive-uri-or-qid> [--ws <local-alias>] [--json]");
|
|
587
|
+
log(" mdkg archive compress --all [--ws <local-alias>] [--json]");
|
|
588
|
+
log("\nNotes:");
|
|
589
|
+
log(" - compression mutates archives owned by enabled local workspaces only");
|
|
590
|
+
log(" - --all excludes read-only imported archive projections and reports them in the receipt");
|
|
591
|
+
log(" - direct imported qids and --ws <imported-alias> fail with source-workspace guidance");
|
|
592
|
+
log(" - all selected archives pass ownership and path preflight before the first write");
|
|
588
593
|
break;
|
|
589
594
|
default:
|
|
590
595
|
log("Usage:");
|
|
@@ -592,7 +597,7 @@ function printArchiveHelp(log, subcommand) {
|
|
|
592
597
|
log(" mdkg archive list [--kind source|artifact] [--visibility private|internal|public] [--json]");
|
|
593
598
|
log(" mdkg archive show <id-or-archive-uri> [--json]");
|
|
594
599
|
log(" mdkg archive verify [id-or-archive-uri] [--json]");
|
|
595
|
-
log(" mdkg archive compress <id-or-archive-uri|--all> [--json]");
|
|
600
|
+
log(" mdkg archive compress <id-or-archive-uri-or-qid|--all> [--ws <local-alias>] [--json]");
|
|
596
601
|
log("\nNotes:");
|
|
597
602
|
log(" - archive add copies the source, writes a sidecar, and writes a deterministic zip cache");
|
|
598
603
|
log(" - archive visibility defaults to private");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": 1,
|
|
3
3
|
"tool": "mdkg",
|
|
4
|
-
"package_version": "0.5.
|
|
4
|
+
"package_version": "0.5.1",
|
|
5
5
|
"source": {
|
|
6
6
|
"help_targets": "scripts/cli_help_targets.js",
|
|
7
7
|
"command_matrix": "CLI_COMMAND_MATRIX.md"
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
" mdkg archive list [--kind source|artifact] [--visibility private|internal|public] [--json]",
|
|
26
26
|
" mdkg archive show <id-or-archive-uri> [--json]",
|
|
27
27
|
" mdkg archive verify [id-or-archive-uri] [--json]",
|
|
28
|
-
" mdkg archive compress <id-or-archive-uri|--all> [--json]"
|
|
28
|
+
" mdkg archive compress <id-or-archive-uri-or-qid|--all> [--ws <local-alias>] [--json]"
|
|
29
29
|
],
|
|
30
30
|
"args": [
|
|
31
31
|
{
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
"source": "<id-or-archive-uri>"
|
|
40
40
|
},
|
|
41
41
|
{
|
|
42
|
-
"name": "id-or-archive-uri|--all",
|
|
42
|
+
"name": "id-or-archive-uri-or-qid|--all",
|
|
43
43
|
"required": true,
|
|
44
|
-
"source": "<id-or-archive-uri|--all>"
|
|
44
|
+
"source": "<id-or-archive-uri-or-qid|--all>"
|
|
45
45
|
}
|
|
46
46
|
],
|
|
47
47
|
"flags": [
|
|
@@ -203,22 +203,22 @@
|
|
|
203
203
|
"visibility": "public",
|
|
204
204
|
"summary": "mdkg archive compress command",
|
|
205
205
|
"usage": [
|
|
206
|
-
" mdkg archive compress <id-or-archive-uri> [--ws <alias>] [--json]",
|
|
207
|
-
" mdkg archive compress --all [--json]"
|
|
206
|
+
" mdkg archive compress <id-or-archive-uri-or-qid> [--ws <local-alias>] [--json]",
|
|
207
|
+
" mdkg archive compress --all [--ws <local-alias>] [--json]"
|
|
208
208
|
],
|
|
209
209
|
"args": [
|
|
210
210
|
{
|
|
211
|
-
"name": "id-or-archive-uri",
|
|
211
|
+
"name": "id-or-archive-uri-or-qid",
|
|
212
212
|
"required": true,
|
|
213
|
-
"source": "<id-or-archive-uri>"
|
|
213
|
+
"source": "<id-or-archive-uri-or-qid>"
|
|
214
214
|
}
|
|
215
215
|
],
|
|
216
216
|
"flags": [
|
|
217
217
|
{
|
|
218
218
|
"name": "--all",
|
|
219
|
-
"value": "[--
|
|
219
|
+
"value": "[--ws <local-alias>]",
|
|
220
220
|
"required": false,
|
|
221
|
-
"description": "mdkg archive compress --all [--json]"
|
|
221
|
+
"description": "mdkg archive compress --all [--ws <local-alias>] [--json]"
|
|
222
222
|
},
|
|
223
223
|
{
|
|
224
224
|
"name": "--help",
|
|
@@ -237,6 +237,12 @@
|
|
|
237
237
|
"value": null,
|
|
238
238
|
"required": false,
|
|
239
239
|
"description": "--version, -V Show version"
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
"name": "--ws",
|
|
243
|
+
"value": "<imported-alias>",
|
|
244
|
+
"required": false,
|
|
245
|
+
"description": "- direct imported qids and --ws <imported-alias> fail with source-workspace guidance"
|
|
240
246
|
}
|
|
241
247
|
],
|
|
242
248
|
"output_formats": [
|
|
@@ -245,7 +251,7 @@
|
|
|
245
251
|
],
|
|
246
252
|
"json_schema_ref": "mdkg.command_output.v1",
|
|
247
253
|
"side_effects": [
|
|
248
|
-
"refresh-archive-zip-
|
|
254
|
+
"refresh-local-workspace-archive-zip-caches"
|
|
249
255
|
],
|
|
250
256
|
"read_paths": [
|
|
251
257
|
".mdkg/**"
|
|
@@ -258,9 +264,11 @@
|
|
|
258
264
|
"supported": false
|
|
259
265
|
},
|
|
260
266
|
"lock_policy": "mutation-lock-required",
|
|
261
|
-
"atomic_write_policy": "
|
|
267
|
+
"atomic_write_policy": "full-selection-preflight-then-per-file-atomic-replacement",
|
|
262
268
|
"receipts": [
|
|
263
|
-
"archive-compress-receipt"
|
|
269
|
+
"archive-compress-receipt",
|
|
270
|
+
"archive-workspace-selection-receipt",
|
|
271
|
+
"read-only-exclusion-receipt"
|
|
264
272
|
],
|
|
265
273
|
"danger_level": "moderate",
|
|
266
274
|
"aliases": [],
|
|
@@ -275,8 +283,8 @@
|
|
|
275
283
|
}
|
|
276
284
|
],
|
|
277
285
|
"examples": [
|
|
278
|
-
" mdkg archive compress --all [--json]",
|
|
279
|
-
" mdkg archive compress <id-or-archive-uri> [--ws <alias>] [--json]"
|
|
286
|
+
" mdkg archive compress --all [--ws <local-alias>] [--json]",
|
|
287
|
+
" mdkg archive compress <id-or-archive-uri-or-qid> [--ws <local-alias>] [--json]"
|
|
280
288
|
],
|
|
281
289
|
"docs": {
|
|
282
290
|
"help_target": "mdkg help archive compress",
|
|
@@ -10548,5 +10556,5 @@
|
|
|
10548
10556
|
}
|
|
10549
10557
|
}
|
|
10550
10558
|
],
|
|
10551
|
-
"contract_hash": "
|
|
10559
|
+
"contract_hash": "1d137b3c750bc1d8b9bcb863123719d070291266582c8aec20f8e21529434d54"
|
|
10552
10560
|
}
|
package/dist/commands/archive.js
CHANGED
|
@@ -83,6 +83,22 @@ function normalizeArchiveId(raw) {
|
|
|
83
83
|
}
|
|
84
84
|
return normalized;
|
|
85
85
|
}
|
|
86
|
+
function archiveTargetFromInput(value, ws) {
|
|
87
|
+
const uriId = (0, refs_1.archiveIdFromUri)(value);
|
|
88
|
+
if (uriId) {
|
|
89
|
+
return { id: normalizeArchiveId(uriId), workspace: normalizeWorkspace(ws) };
|
|
90
|
+
}
|
|
91
|
+
const separator = value.indexOf(":");
|
|
92
|
+
if (separator > 0) {
|
|
93
|
+
const workspace = normalizeWorkspace(value.slice(0, separator));
|
|
94
|
+
const id = normalizeArchiveId(value.slice(separator + 1));
|
|
95
|
+
if (ws && normalizeWorkspace(ws) !== workspace) {
|
|
96
|
+
throw new errors_1.UsageError(`archive qid ${value} conflicts with --ws ${normalizeWorkspace(ws)}`);
|
|
97
|
+
}
|
|
98
|
+
return { id, workspace };
|
|
99
|
+
}
|
|
100
|
+
return { id: normalizeArchiveId(value), workspace: normalizeWorkspace(ws) };
|
|
101
|
+
}
|
|
86
102
|
function archiveIdFromInput(value) {
|
|
87
103
|
return normalizeArchiveId((0, refs_1.archiveIdFromUri)(value) ?? value);
|
|
88
104
|
}
|
|
@@ -133,9 +149,8 @@ function maybeReindex(root) {
|
|
|
133
149
|
function resolveArchiveNode(root, id, ws) {
|
|
134
150
|
const config = (0, config_1.loadConfig)(root);
|
|
135
151
|
const { index } = (0, index_cache_1.loadIndex)({ root, config });
|
|
136
|
-
const
|
|
137
|
-
const
|
|
138
|
-
const qid = archiveId.includes(":") ? archiveId : `${wsHint}:${archiveId}`;
|
|
152
|
+
const target = archiveTargetFromInput(id, ws);
|
|
153
|
+
const qid = `${target.workspace}:${target.id}`;
|
|
139
154
|
const node = index.nodes[qid];
|
|
140
155
|
if (!node || node.type !== "archive") {
|
|
141
156
|
throw new errors_1.NotFoundError(`archive not found: ${id}`);
|
|
@@ -143,6 +158,9 @@ function resolveArchiveNode(root, id, ws) {
|
|
|
143
158
|
return node;
|
|
144
159
|
}
|
|
145
160
|
function archiveNodePaths(root, node) {
|
|
161
|
+
if (node.source?.imported || node.source?.read_only || node.path.includes("#")) {
|
|
162
|
+
throw new errors_1.ValidationError(`refusing to derive filesystem paths for read-only archive projection ${node.qid}`);
|
|
163
|
+
}
|
|
146
164
|
const sidecarPath = path_1.default.resolve(root, node.path);
|
|
147
165
|
const sidecarDir = path_1.default.dirname(sidecarPath);
|
|
148
166
|
return {
|
|
@@ -175,9 +193,12 @@ function stringAttribute(value) {
|
|
|
175
193
|
return typeof value === "string" && value.trim().length > 0 ? value : undefined;
|
|
176
194
|
}
|
|
177
195
|
function writeArchiveSidecar(root, sidecarPath, frontmatter, body) {
|
|
196
|
+
(0, filesystem_authority_1.atomicReplaceContainedFile)({ root, relativePath: toPosixPath(path_1.default.relative(root, sidecarPath)) }, formatArchiveSidecar(frontmatter, body));
|
|
197
|
+
}
|
|
198
|
+
function formatArchiveSidecar(frontmatter, body) {
|
|
178
199
|
const lines = (0, frontmatter_1.formatFrontmatter)(frontmatter);
|
|
179
200
|
const content = ["---", ...lines, "---", body.trimStart()].join("\n");
|
|
180
|
-
|
|
201
|
+
return content.endsWith("\n") ? content : `${content}\n`;
|
|
181
202
|
}
|
|
182
203
|
function verifyArchiveSidecar(root, ws, sidecarPath) {
|
|
183
204
|
const relativePath = toPosixPath(path_1.default.relative(root, sidecarPath));
|
|
@@ -436,42 +457,137 @@ function runArchiveVerifyCommand(options) {
|
|
|
436
457
|
throw new errors_1.ValidationError("archive verification failed");
|
|
437
458
|
}
|
|
438
459
|
}
|
|
439
|
-
function
|
|
440
|
-
|
|
441
|
-
|
|
460
|
+
function isReadOnlyArchiveProjection(config, node) {
|
|
461
|
+
return Boolean(node.source?.imported ||
|
|
462
|
+
node.source?.read_only ||
|
|
463
|
+
config.subgraphs[node.source?.subgraph_alias ?? node.ws]);
|
|
464
|
+
}
|
|
465
|
+
function readOnlyArchiveError(node) {
|
|
466
|
+
const alias = node.source?.subgraph_alias ?? node.ws;
|
|
467
|
+
return new errors_1.ValidationError(`cannot compress read-only archive ${node.qid} from imported workspace ${alias}; ` +
|
|
468
|
+
"run compression in the source workspace and refresh the subgraph bundle");
|
|
469
|
+
}
|
|
470
|
+
function readOnlyWorkspaceError(alias) {
|
|
471
|
+
return new errors_1.ValidationError(`cannot compress archives in read-only imported workspace ${alias}; ` +
|
|
472
|
+
"run compression in the source workspace and refresh the subgraph bundle");
|
|
473
|
+
}
|
|
474
|
+
function assertWritableArchiveOwner(config, node) {
|
|
475
|
+
if (isReadOnlyArchiveProjection(config, node)) {
|
|
476
|
+
throw readOnlyArchiveError(node);
|
|
442
477
|
}
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
478
|
+
const workspace = config.workspaces[node.ws];
|
|
479
|
+
if (!workspace) {
|
|
480
|
+
throw new errors_1.ValidationError(`archive ${node.qid} has no configured local workspace owner`);
|
|
481
|
+
}
|
|
482
|
+
if (!workspace.enabled) {
|
|
483
|
+
throw new errors_1.ValidationError(`archive workspace ${node.ws} is disabled and not writable`);
|
|
484
|
+
}
|
|
485
|
+
const archiveRoot = (0, workspace_path_1.workspaceDocumentRelativePath)(workspace.path, workspace.mdkg_dir, "archive");
|
|
486
|
+
const nodePath = toPosixPath(node.path);
|
|
487
|
+
if (!nodePath.startsWith(`${archiveRoot}/`)) {
|
|
488
|
+
throw new errors_1.ValidationError(`archive ${node.qid} path is outside its configured local archive root: ${archiveRoot}`);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
function resolveArchiveNodeFromIndex(index, id, ws) {
|
|
492
|
+
const target = archiveTargetFromInput(id, ws);
|
|
493
|
+
const qid = `${target.workspace}:${target.id}`;
|
|
494
|
+
const node = index.nodes[qid];
|
|
495
|
+
if (!node || node.type !== "archive") {
|
|
496
|
+
throw new errors_1.NotFoundError(`archive not found: ${id}`);
|
|
497
|
+
}
|
|
498
|
+
return node;
|
|
499
|
+
}
|
|
500
|
+
function selectArchiveCompressionNodes(options, config, index) {
|
|
501
|
+
if (options.all && options.id) {
|
|
502
|
+
throw new errors_1.UsageError("archive compress accepts either <id-or-archive-uri-or-qid> or --all, not both");
|
|
503
|
+
}
|
|
504
|
+
const requestedWorkspace = options.ws ? normalizeWorkspace(options.ws) : null;
|
|
505
|
+
const excluded = [];
|
|
506
|
+
let nodes;
|
|
507
|
+
if (options.all) {
|
|
508
|
+
if (requestedWorkspace && config.subgraphs[requestedWorkspace]) {
|
|
509
|
+
throw readOnlyWorkspaceError(requestedWorkspace);
|
|
457
510
|
}
|
|
458
|
-
if (
|
|
459
|
-
|
|
511
|
+
if (requestedWorkspace) {
|
|
512
|
+
const workspace = config.workspaces[requestedWorkspace];
|
|
513
|
+
if (!workspace) {
|
|
514
|
+
throw new errors_1.NotFoundError(`workspace not found: ${requestedWorkspace}`);
|
|
515
|
+
}
|
|
516
|
+
if (!workspace.enabled) {
|
|
517
|
+
throw new errors_1.ValidationError(`archive workspace ${requestedWorkspace} is disabled and not writable`);
|
|
518
|
+
}
|
|
460
519
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
520
|
+
nodes = Object.values(index.nodes)
|
|
521
|
+
.filter((node) => node.type === "archive")
|
|
522
|
+
.sort((a, b) => a.qid.localeCompare(b.qid))
|
|
523
|
+
.filter((node) => {
|
|
524
|
+
if (requestedWorkspace && node.ws !== requestedWorkspace) {
|
|
525
|
+
return false;
|
|
526
|
+
}
|
|
527
|
+
if (isReadOnlyArchiveProjection(config, node)) {
|
|
528
|
+
if (!requestedWorkspace) {
|
|
529
|
+
excluded.push({
|
|
530
|
+
workspace: node.ws,
|
|
531
|
+
qid: node.qid,
|
|
532
|
+
reason: "read_only_imported_subgraph",
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
return false;
|
|
536
|
+
}
|
|
537
|
+
assertWritableArchiveOwner(config, node);
|
|
538
|
+
return true;
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
else {
|
|
542
|
+
const node = resolveArchiveNodeFromIndex(index, String(options.id), options.ws);
|
|
543
|
+
assertWritableArchiveOwner(config, node);
|
|
544
|
+
nodes = [node];
|
|
545
|
+
}
|
|
546
|
+
return {
|
|
547
|
+
nodes,
|
|
548
|
+
selection: {
|
|
549
|
+
requested_workspace: requestedWorkspace,
|
|
550
|
+
selected_workspaces: Array.from(new Set(nodes.map((node) => node.ws))).sort(),
|
|
551
|
+
excluded_read_only: excluded.sort((a, b) => a.qid.localeCompare(b.qid)),
|
|
552
|
+
},
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
function preflightArchiveCompression(root, node, today) {
|
|
556
|
+
for (const attribute of ["stored_path", "compressed_path"]) {
|
|
557
|
+
if (typeof node.attributes[attribute] !== "string" || !String(node.attributes[attribute]).trim()) {
|
|
558
|
+
throw new errors_1.ValidationError(`${node.qid}: ${attribute} is required before archive compression`);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
const { sidecarPath, rawPath, zipPath } = archiveNodePaths(root, node);
|
|
562
|
+
const rawRelativePath = toPosixPath(path_1.default.relative(root, rawPath));
|
|
563
|
+
const zipRelativePath = toPosixPath(path_1.default.relative(root, zipPath));
|
|
564
|
+
const sidecarRelativePath = toPosixPath(path_1.default.relative(root, sidecarPath));
|
|
565
|
+
for (const relativePath of [rawRelativePath, zipRelativePath, sidecarRelativePath]) {
|
|
566
|
+
(0, filesystem_authority_1.withContainedPathSink)({ root, relativePath, operation: relativePath === rawRelativePath ? "read" : "replace" }, () => undefined);
|
|
567
|
+
}
|
|
568
|
+
if (!(0, filesystem_authority_1.containedPathExists)({ root, relativePath: rawRelativePath })) {
|
|
569
|
+
throw new errors_1.NotFoundError(`raw archive file missing for ${node.qid}: ${path_1.default.relative(root, rawPath)}`);
|
|
570
|
+
}
|
|
571
|
+
const rawData = (0, filesystem_authority_1.readContainedFile)({ root, relativePath: rawRelativePath }, null);
|
|
572
|
+
const parsed = (0, frontmatter_1.parseFrontmatter)((0, filesystem_authority_1.readContainedFile)({ root, relativePath: sidecarRelativePath }), sidecarPath);
|
|
573
|
+
if (parsed.frontmatter.type !== "archive" || parsed.frontmatter.id !== node.id) {
|
|
574
|
+
throw new errors_1.ValidationError(`${node.qid}: archive sidecar identity changed before compression`);
|
|
575
|
+
}
|
|
576
|
+
const zipData = (0, zip_1.createDeterministicZip)(path_1.default.basename(rawPath), rawData);
|
|
577
|
+
const nextFrontmatter = {
|
|
578
|
+
...parsed.frontmatter,
|
|
579
|
+
byte_size: String(rawData.length),
|
|
580
|
+
sha256: (0, archive_integrity_1.hashArchiveBuffer)(rawData),
|
|
581
|
+
compressed_sha256: (0, archive_integrity_1.hashArchiveBuffer)(zipData),
|
|
582
|
+
ingest_status: "compressed",
|
|
583
|
+
updated: today,
|
|
584
|
+
};
|
|
585
|
+
return {
|
|
586
|
+
zipRelativePath,
|
|
587
|
+
sidecarRelativePath,
|
|
588
|
+
zipData,
|
|
589
|
+
sidecarContent: formatArchiveSidecar(nextFrontmatter, parsed.body),
|
|
590
|
+
receipt: {
|
|
475
591
|
workspace: node.ws,
|
|
476
592
|
id: node.id,
|
|
477
593
|
qid: node.qid,
|
|
@@ -482,14 +598,35 @@ function runArchiveCompressCommandLocked(options) {
|
|
|
482
598
|
sha256: String(nextFrontmatter.sha256),
|
|
483
599
|
compressed_sha256: String(nextFrontmatter.compressed_sha256),
|
|
484
600
|
visibility: (0, visibility_1.normalizeVisibility)(typeof nextFrontmatter.visibility === "string" ? nextFrontmatter.visibility : undefined, "archive visibility"),
|
|
485
|
-
}
|
|
601
|
+
},
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
function runArchiveCompressCommandLocked(options) {
|
|
605
|
+
if (!options.all && !options.id) {
|
|
606
|
+
throw new errors_1.UsageError("archive compress requires <id-or-archive-uri-or-qid> or --all");
|
|
607
|
+
}
|
|
608
|
+
const config = (0, config_1.loadConfig)(options.root);
|
|
609
|
+
const { index } = (0, index_cache_1.loadIndex)({ root: options.root, config });
|
|
610
|
+
const { nodes, selection } = selectArchiveCompressionNodes(options, config, index);
|
|
611
|
+
const today = (0, date_1.formatDate)(options.now ?? new Date());
|
|
612
|
+
const plans = nodes.map((node) => preflightArchiveCompression(options.root, node, today));
|
|
613
|
+
const updated = [];
|
|
614
|
+
for (const plan of plans) {
|
|
615
|
+
(0, filesystem_authority_1.atomicReplaceContainedFile)({ root: options.root, relativePath: plan.zipRelativePath }, plan.zipData);
|
|
616
|
+
(0, filesystem_authority_1.atomicReplaceContainedFile)({ root: options.root, relativePath: plan.sidecarRelativePath }, plan.sidecarContent);
|
|
617
|
+
updated.push(plan.receipt);
|
|
486
618
|
}
|
|
487
619
|
maybeReindex(options.root);
|
|
488
620
|
if (options.json) {
|
|
489
|
-
console.log(JSON.stringify({ action: "compressed", count: updated.length, archives: updated }, null, 2));
|
|
621
|
+
console.log(JSON.stringify({ action: "compressed", count: updated.length, archives: updated, selection }, null, 2));
|
|
490
622
|
return;
|
|
491
623
|
}
|
|
492
624
|
console.log(`archive compressed: ${updated.length}`);
|
|
625
|
+
console.log(`selected workspaces: ${selection.selected_workspaces.join(", ") || "none"}`);
|
|
626
|
+
console.log(`excluded read-only projections: ${selection.excluded_read_only.length}`);
|
|
627
|
+
for (const excluded of selection.excluded_read_only) {
|
|
628
|
+
console.log(` - ${excluded.qid} (${excluded.reason})`);
|
|
629
|
+
}
|
|
493
630
|
}
|
|
494
631
|
function withArchiveLock(root, fn) {
|
|
495
632
|
const config = (0, config_1.loadConfig)(root);
|