daftari 1.31.0 → 1.32.0
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 +70 -0
- package/README.md +58 -9
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +6 -0
- package/dist/cli.js.map +1 -1
- package/dist/consolidate/index.d.ts.map +1 -1
- package/dist/consolidate/index.js +10 -12
- package/dist/consolidate/index.js.map +1 -1
- package/dist/curation/edges.d.ts.map +1 -1
- package/dist/curation/edges.js +36 -17
- package/dist/curation/edges.js.map +1 -1
- package/dist/curation/lint.d.ts +2 -0
- package/dist/curation/lint.d.ts.map +1 -1
- package/dist/curation/lint.js +26 -18
- package/dist/curation/lint.js.map +1 -1
- package/dist/curation/shadow.d.ts +1 -0
- package/dist/curation/shadow.d.ts.map +1 -1
- package/dist/curation/shadow.js +17 -11
- package/dist/curation/shadow.js.map +1 -1
- package/dist/curation/staged-actions.d.ts +1 -0
- package/dist/curation/staged-actions.d.ts.map +1 -1
- package/dist/curation/staged-actions.js +21 -24
- package/dist/curation/staged-actions.js.map +1 -1
- package/dist/eval/generate.d.ts.map +1 -1
- package/dist/eval/generate.js +16 -0
- package/dist/eval/generate.js.map +1 -1
- package/dist/eval/index.d.ts +1 -0
- package/dist/eval/index.d.ts.map +1 -1
- package/dist/eval/index.js +79 -28
- package/dist/eval/index.js.map +1 -1
- package/dist/eval/llm-openrouter.d.ts.map +1 -1
- package/dist/eval/llm-openrouter.js +158 -65
- package/dist/eval/llm-openrouter.js.map +1 -1
- package/dist/interview/index.d.ts +7 -0
- package/dist/interview/index.d.ts.map +1 -0
- package/dist/interview/index.js +219 -0
- package/dist/interview/index.js.map +1 -0
- package/dist/interview/questions.d.ts +18 -0
- package/dist/interview/questions.d.ts.map +1 -0
- package/dist/interview/questions.js +169 -0
- package/dist/interview/questions.js.map +1 -0
- package/dist/interview/transcript.d.ts +23 -0
- package/dist/interview/transcript.d.ts.map +1 -0
- package/dist/interview/transcript.js +155 -0
- package/dist/interview/transcript.js.map +1 -0
- package/dist/okf/export.d.ts.map +1 -1
- package/dist/okf/export.js +4 -2
- package/dist/okf/export.js.map +1 -1
- package/dist/okf/import.d.ts.map +1 -1
- package/dist/okf/import.js +11 -3
- package/dist/okf/import.js.map +1 -1
- package/dist/okf/index.d.ts.map +1 -1
- package/dist/okf/index.js +13 -1
- package/dist/okf/index.js.map +1 -1
- package/dist/okf/map.d.ts +11 -0
- package/dist/okf/map.d.ts.map +1 -1
- package/dist/okf/map.js +191 -13
- package/dist/okf/map.js.map +1 -1
- package/dist/okf/types.d.ts +6 -2
- package/dist/okf/types.d.ts.map +1 -1
- package/dist/okf/types.js +39 -5
- package/dist/okf/types.js.map +1 -1
- package/dist/search/hybrid.d.ts.map +1 -1
- package/dist/search/hybrid.js +2 -5
- package/dist/search/hybrid.js.map +1 -1
- package/dist/search/reindex.d.ts +3 -0
- package/dist/search/reindex.d.ts.map +1 -1
- package/dist/search/reindex.js +30 -10
- package/dist/search/reindex.js.map +1 -1
- package/dist/search/watcher.d.ts.map +1 -1
- package/dist/search/watcher.js +8 -17
- package/dist/search/watcher.js.map +1 -1
- package/dist/storage/index-db.d.ts.map +1 -1
- package/dist/storage/index-db.js +1 -0
- package/dist/storage/index-db.js.map +1 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +69 -47
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/staged-actions.d.ts.map +1 -1
- package/dist/tools/staged-actions.js +32 -34
- package/dist/tools/staged-actions.js.map +1 -1
- package/dist/tools/write.d.ts.map +1 -1
- package/dist/tools/write.js +152 -216
- package/dist/tools/write.js.map +1 -1
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +86 -86
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/dates.d.ts +1 -0
- package/dist/utils/dates.d.ts.map +1 -1
- package/dist/utils/dates.js +5 -0
- package/dist/utils/dates.js.map +1 -1
- package/package.json +1 -1
package/dist/tools/write.js
CHANGED
|
@@ -338,6 +338,64 @@ function denyIfProposeOnly(access, tool) {
|
|
|
338
338
|
}
|
|
339
339
|
return ok(undefined);
|
|
340
340
|
}
|
|
341
|
+
// The uniform RBAC write gate. One helper because the denial string is part
|
|
342
|
+
// of the tool contract — every write tool emits the identical message shape,
|
|
343
|
+
// and it must never leak more than the collection name.
|
|
344
|
+
function requireWriteAccess(access, collection) {
|
|
345
|
+
if (access && !canWrite(access.role, collection)) {
|
|
346
|
+
return err(new Error(`access denied: role '${access.roleName}' cannot write to ` + `collection '${collection}'`));
|
|
347
|
+
}
|
|
348
|
+
return ok(undefined);
|
|
349
|
+
}
|
|
350
|
+
async function loadTargetDocument(vaultRoot, rawPath, tool) {
|
|
351
|
+
const resolved = resolveVaultPath(vaultRoot, rawPath);
|
|
352
|
+
if (!resolved.ok)
|
|
353
|
+
return resolved;
|
|
354
|
+
const existing = await readFile(resolved.value.absPath);
|
|
355
|
+
if (!existing.ok) {
|
|
356
|
+
return err(new Error(`${tool}: document not found: ${rawPath}`));
|
|
357
|
+
}
|
|
358
|
+
const parsed = parseDocument(existing.value);
|
|
359
|
+
if (!parsed.ok)
|
|
360
|
+
return parsed;
|
|
361
|
+
const config = loadConfig(vaultRoot);
|
|
362
|
+
if (!config.ok)
|
|
363
|
+
return config;
|
|
364
|
+
return ok({
|
|
365
|
+
// Lock key, provenance, and commit path are all keyed on the CANONICAL
|
|
366
|
+
// relPath, never the raw caller string: aliased spellings of one file
|
|
367
|
+
// must contend on one lock (#127/#128).
|
|
368
|
+
relPath: resolved.value.relPath,
|
|
369
|
+
absPath: resolved.value.absPath,
|
|
370
|
+
parsed: parsed.value,
|
|
371
|
+
config: config.value,
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
// The shared performWrite tail for frontmatter-only mutations: body preserved
|
|
375
|
+
// verbatim (bodyChanged false), file text re-serialized from the new
|
|
376
|
+
// frontmatter over the existing content.
|
|
377
|
+
function performFrontmatterWrite(opts) {
|
|
378
|
+
const { target } = opts;
|
|
379
|
+
return performWrite({
|
|
380
|
+
vaultRoot: opts.vaultRoot,
|
|
381
|
+
relPath: target.relPath,
|
|
382
|
+
absPath: target.absPath,
|
|
383
|
+
agent: opts.agent,
|
|
384
|
+
tool: opts.tool,
|
|
385
|
+
action: opts.action,
|
|
386
|
+
fileText: serializeDocument(opts.newFrontmatter, target.parsed.content, target.config.schemaExtensions, target.parsed.raw),
|
|
387
|
+
newFrontmatter: opts.newFrontmatter,
|
|
388
|
+
oldFrontmatter: target.parsed.frontmatter,
|
|
389
|
+
validation: target.parsed.validation,
|
|
390
|
+
commitMessage: opts.commitMessage,
|
|
391
|
+
autoCommit: target.config.autoCommit,
|
|
392
|
+
gitDir: target.config.gitDir,
|
|
393
|
+
baseVersion: opts.baseVersion,
|
|
394
|
+
shadowMode: target.config.shadowMode,
|
|
395
|
+
principal: opts.access?.user,
|
|
396
|
+
bodyChanged: false,
|
|
397
|
+
});
|
|
398
|
+
}
|
|
341
399
|
// ---------------------------------------------------------------------------
|
|
342
400
|
// vault_write
|
|
343
401
|
// ---------------------------------------------------------------------------
|
|
@@ -470,13 +528,9 @@ export async function vaultWrite(vaultRoot, args, access) {
|
|
|
470
528
|
// collection is allowed to differ from the physical dir (e.g. a draft staged
|
|
471
529
|
// in _drafts/ that declares its destination collection); it just never widens
|
|
472
530
|
// access.
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
return err(new Error(`access denied: role '${access.roleName}' cannot write to ` +
|
|
477
|
-
`collection '${collection}'`));
|
|
478
|
-
}
|
|
479
|
-
}
|
|
531
|
+
const writeGate = requireWriteAccess(access, targetCollection(vaultRoot, path.value));
|
|
532
|
+
if (!writeGate.ok)
|
|
533
|
+
return writeGate;
|
|
480
534
|
const resolved = resolveVaultPath(vaultRoot, path.value);
|
|
481
535
|
if (!resolved.ok)
|
|
482
536
|
return resolved;
|
|
@@ -775,13 +829,9 @@ export async function vaultAppend(vaultRoot, args, access) {
|
|
|
775
829
|
return config;
|
|
776
830
|
const extensions = config.value.schemaExtensions;
|
|
777
831
|
const oldFrontmatter = parsed.value.frontmatter;
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
return err(new Error(`access denied: role '${access.roleName}' cannot write to ` +
|
|
782
|
-
`collection '${collection}'`));
|
|
783
|
-
}
|
|
784
|
-
}
|
|
832
|
+
const rbac = requireWriteAccess(access, collectionOf(path.value, oldFrontmatter));
|
|
833
|
+
if (!rbac.ok)
|
|
834
|
+
return rbac;
|
|
785
835
|
const newFrontmatter = {
|
|
786
836
|
...oldFrontmatter,
|
|
787
837
|
updated: todayISO(),
|
|
@@ -875,65 +925,42 @@ export async function vaultPromote(vaultRoot, args, access) {
|
|
|
875
925
|
if (access && !canPromote(access.role)) {
|
|
876
926
|
return err(new Error(`access denied: role '${access.roleName}' cannot promote documents`));
|
|
877
927
|
}
|
|
878
|
-
const
|
|
879
|
-
if (!
|
|
880
|
-
return
|
|
881
|
-
const
|
|
882
|
-
if (!existing.ok) {
|
|
883
|
-
return err(new Error(`vault_promote: document not found: ${path.value}`));
|
|
884
|
-
}
|
|
885
|
-
const parsed = parseDocument(existing.value);
|
|
886
|
-
if (!parsed.ok)
|
|
887
|
-
return parsed;
|
|
888
|
-
const config = loadConfig(vaultRoot);
|
|
889
|
-
if (!config.ok)
|
|
890
|
-
return config;
|
|
891
|
-
const extensions = config.value.schemaExtensions;
|
|
892
|
-
const oldFrontmatter = parsed.value.frontmatter;
|
|
928
|
+
const target = await loadTargetDocument(vaultRoot, path.value, "vault_promote");
|
|
929
|
+
if (!target.ok)
|
|
930
|
+
return target;
|
|
931
|
+
const oldFrontmatter = target.value.parsed.frontmatter;
|
|
893
932
|
if (oldFrontmatter.status !== "draft") {
|
|
894
933
|
return err(new Error(`vault_promote: only draft documents can be promoted ` +
|
|
895
934
|
`(${path.value} is '${oldFrontmatter.status}')`));
|
|
896
935
|
}
|
|
897
|
-
if (!
|
|
898
|
-
const summary =
|
|
936
|
+
if (!target.value.parsed.validation.valid) {
|
|
937
|
+
const summary = target.value.parsed.validation.issues
|
|
899
938
|
.map((i) => `${i.field}: ${i.message}`)
|
|
900
939
|
.join("; ");
|
|
901
940
|
return err(new Error(`vault_promote: frontmatter is incomplete: ${summary}`));
|
|
902
941
|
}
|
|
903
942
|
// `confidence set` — the document must declare a confidence explicitly, not
|
|
904
943
|
// ride on the validator's default.
|
|
905
|
-
const rawConfidence =
|
|
944
|
+
const rawConfidence = target.value.parsed.raw.confidence;
|
|
906
945
|
if (typeof rawConfidence !== "string" ||
|
|
907
946
|
!CONFIDENCES.includes(rawConfidence)) {
|
|
908
947
|
return err(new Error("vault_promote: confidence must be set before promotion"));
|
|
909
948
|
}
|
|
910
|
-
|
|
911
|
-
...oldFrontmatter,
|
|
912
|
-
status: "canonical",
|
|
913
|
-
updated: todayISO(),
|
|
914
|
-
updated_by: agent.value,
|
|
915
|
-
};
|
|
916
|
-
return performWrite({
|
|
949
|
+
return performFrontmatterWrite({
|
|
917
950
|
vaultRoot,
|
|
918
|
-
|
|
919
|
-
// relPath (resolved.value.relPath), never the raw caller string: aliased
|
|
920
|
-
// spellings of one file must contend on one lock (#127/#128).
|
|
921
|
-
relPath: resolved.value.relPath,
|
|
922
|
-
absPath: resolved.value.absPath,
|
|
951
|
+
target: target.value,
|
|
923
952
|
agent: agent.value,
|
|
924
953
|
tool: "vault_promote",
|
|
925
954
|
action: "promote",
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
955
|
+
newFrontmatter: {
|
|
956
|
+
...oldFrontmatter,
|
|
957
|
+
status: "canonical",
|
|
958
|
+
updated: todayISO(),
|
|
959
|
+
updated_by: agent.value,
|
|
960
|
+
},
|
|
930
961
|
commitMessage: `vault_promote: ${path.value} draft→canonical by ${agent.value}`,
|
|
931
|
-
autoCommit: config.value.autoCommit,
|
|
932
|
-
gitDir: config.value.gitDir,
|
|
933
962
|
baseVersion: baseVersion.value,
|
|
934
|
-
|
|
935
|
-
principal: access?.user,
|
|
936
|
-
bodyChanged: false,
|
|
963
|
+
access,
|
|
937
964
|
});
|
|
938
965
|
}
|
|
939
966
|
// ---------------------------------------------------------------------------
|
|
@@ -968,57 +995,30 @@ export async function vaultDeprecate(vaultRoot, args, access) {
|
|
|
968
995
|
}
|
|
969
996
|
supersededBy = args.superseded_by;
|
|
970
997
|
}
|
|
971
|
-
const
|
|
972
|
-
if (!
|
|
973
|
-
return
|
|
974
|
-
const
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
if (!parsed.ok)
|
|
980
|
-
return parsed;
|
|
981
|
-
const config = loadConfig(vaultRoot);
|
|
982
|
-
if (!config.ok)
|
|
983
|
-
return config;
|
|
984
|
-
const extensions = config.value.schemaExtensions;
|
|
985
|
-
const oldFrontmatter = parsed.value.frontmatter;
|
|
986
|
-
if (access) {
|
|
987
|
-
const collection = collectionOf(path.value, oldFrontmatter);
|
|
988
|
-
if (!canWrite(access.role, collection)) {
|
|
989
|
-
return err(new Error(`access denied: role '${access.roleName}' cannot write to ` +
|
|
990
|
-
`collection '${collection}'`));
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
const newFrontmatter = {
|
|
994
|
-
...oldFrontmatter,
|
|
995
|
-
status: "deprecated",
|
|
996
|
-
superseded_by: supersededBy,
|
|
997
|
-
updated: todayISO(),
|
|
998
|
-
updated_by: agent.value,
|
|
999
|
-
};
|
|
1000
|
-
return performWrite({
|
|
998
|
+
const target = await loadTargetDocument(vaultRoot, path.value, "vault_deprecate");
|
|
999
|
+
if (!target.ok)
|
|
1000
|
+
return target;
|
|
1001
|
+
const oldFrontmatter = target.value.parsed.frontmatter;
|
|
1002
|
+
const rbac = requireWriteAccess(access, collectionOf(path.value, oldFrontmatter));
|
|
1003
|
+
if (!rbac.ok)
|
|
1004
|
+
return rbac;
|
|
1005
|
+
return performFrontmatterWrite({
|
|
1001
1006
|
vaultRoot,
|
|
1002
|
-
|
|
1003
|
-
// relPath (resolved.value.relPath), never the raw caller string: aliased
|
|
1004
|
-
// spellings of one file must contend on one lock (#127/#128).
|
|
1005
|
-
relPath: resolved.value.relPath,
|
|
1006
|
-
absPath: resolved.value.absPath,
|
|
1007
|
+
target: target.value,
|
|
1007
1008
|
agent: agent.value,
|
|
1008
1009
|
tool: "vault_deprecate",
|
|
1009
1010
|
action: "deprecate",
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1011
|
+
newFrontmatter: {
|
|
1012
|
+
...oldFrontmatter,
|
|
1013
|
+
status: "deprecated",
|
|
1014
|
+
superseded_by: supersededBy,
|
|
1015
|
+
updated: todayISO(),
|
|
1016
|
+
updated_by: agent.value,
|
|
1017
|
+
},
|
|
1014
1018
|
commitMessage: `vault_deprecate: ${path.value} by ${agent.value} — ${reason.value}` +
|
|
1015
1019
|
(supersededBy ? ` (superseded by ${supersededBy})` : ""),
|
|
1016
|
-
autoCommit: config.value.autoCommit,
|
|
1017
|
-
gitDir: config.value.gitDir,
|
|
1018
1020
|
baseVersion: baseVersion.value,
|
|
1019
|
-
|
|
1020
|
-
principal: access?.user,
|
|
1021
|
-
bodyChanged: false,
|
|
1021
|
+
access,
|
|
1022
1022
|
});
|
|
1023
1023
|
}
|
|
1024
1024
|
// ---------------------------------------------------------------------------
|
|
@@ -1053,28 +1053,13 @@ export async function vaultSetConfidence(vaultRoot, args, access) {
|
|
|
1053
1053
|
const baseVersion = readBaseVersion(args, "vault_set_confidence");
|
|
1054
1054
|
if (!baseVersion.ok)
|
|
1055
1055
|
return baseVersion;
|
|
1056
|
-
const
|
|
1057
|
-
if (!
|
|
1058
|
-
return
|
|
1059
|
-
const
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
const parsed = parseDocument(existing.value);
|
|
1064
|
-
if (!parsed.ok)
|
|
1065
|
-
return parsed;
|
|
1066
|
-
const config = loadConfig(vaultRoot);
|
|
1067
|
-
if (!config.ok)
|
|
1068
|
-
return config;
|
|
1069
|
-
const extensions = config.value.schemaExtensions;
|
|
1070
|
-
const oldFrontmatter = parsed.value.frontmatter;
|
|
1071
|
-
if (access) {
|
|
1072
|
-
const collection = collectionOf(path.value, oldFrontmatter);
|
|
1073
|
-
if (!canWrite(access.role, collection)) {
|
|
1074
|
-
return err(new Error(`access denied: role '${access.roleName}' cannot write to ` +
|
|
1075
|
-
`collection '${collection}'`));
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1056
|
+
const target = await loadTargetDocument(vaultRoot, path.value, "vault_set_confidence");
|
|
1057
|
+
if (!target.ok)
|
|
1058
|
+
return target;
|
|
1059
|
+
const oldFrontmatter = target.value.parsed.frontmatter;
|
|
1060
|
+
const rbac = requireWriteAccess(access, collectionOf(path.value, oldFrontmatter));
|
|
1061
|
+
if (!rbac.ok)
|
|
1062
|
+
return rbac;
|
|
1078
1063
|
// No-op guard: a confidence already at the target would churn a commit for no
|
|
1079
1064
|
// change. Surface it as an error so a redundant staged confidence-up does not
|
|
1080
1065
|
// silently no-op (and a caller learns the value was already set). Compare
|
|
@@ -1083,41 +1068,29 @@ export async function vaultSetConfidence(vaultRoot, args, access) {
|
|
|
1083
1068
|
// validated value would wrongly reject set_confidence(…, "low") on a doc that
|
|
1084
1069
|
// never declared one and never write the field (the trap vault_promote dodges
|
|
1085
1070
|
// the same way, via parsed.value.raw.confidence).
|
|
1086
|
-
const rawConfidence =
|
|
1071
|
+
const rawConfidence = target.value.parsed.raw.confidence;
|
|
1087
1072
|
const currentConfidence = typeof rawConfidence === "string" && CONFIDENCES.includes(rawConfidence)
|
|
1088
1073
|
? rawConfidence
|
|
1089
1074
|
: undefined;
|
|
1090
1075
|
if (currentConfidence === confidence.value) {
|
|
1091
1076
|
return err(new Error(`vault_set_confidence: ${path.value} confidence is already '${confidence.value}'`));
|
|
1092
1077
|
}
|
|
1093
|
-
|
|
1094
|
-
...oldFrontmatter,
|
|
1095
|
-
confidence: confidence.value,
|
|
1096
|
-
updated: todayISO(),
|
|
1097
|
-
updated_by: agent.value,
|
|
1098
|
-
};
|
|
1099
|
-
return performWrite({
|
|
1078
|
+
return performFrontmatterWrite({
|
|
1100
1079
|
vaultRoot,
|
|
1101
|
-
|
|
1102
|
-
// relPath (resolved.value.relPath), never the raw caller string: aliased
|
|
1103
|
-
// spellings of one file must contend on one lock (#127/#128).
|
|
1104
|
-
relPath: resolved.value.relPath,
|
|
1105
|
-
absPath: resolved.value.absPath,
|
|
1080
|
+
target: target.value,
|
|
1106
1081
|
agent: agent.value,
|
|
1107
1082
|
tool: "vault_set_confidence",
|
|
1108
1083
|
action: "confidence-set",
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1084
|
+
newFrontmatter: {
|
|
1085
|
+
...oldFrontmatter,
|
|
1086
|
+
confidence: confidence.value,
|
|
1087
|
+
updated: todayISO(),
|
|
1088
|
+
updated_by: agent.value,
|
|
1089
|
+
},
|
|
1113
1090
|
commitMessage: `vault_set_confidence: ${path.value} ${oldFrontmatter.confidence}→${confidence.value} ` +
|
|
1114
1091
|
`by ${agent.value} — ${reason.value}`,
|
|
1115
|
-
autoCommit: config.value.autoCommit,
|
|
1116
|
-
gitDir: config.value.gitDir,
|
|
1117
1092
|
baseVersion: baseVersion.value,
|
|
1118
|
-
|
|
1119
|
-
principal: access?.user,
|
|
1120
|
-
bodyChanged: false,
|
|
1093
|
+
access,
|
|
1121
1094
|
});
|
|
1122
1095
|
}
|
|
1123
1096
|
// ---------------------------------------------------------------------------
|
|
@@ -1156,28 +1129,13 @@ export async function vaultSetTier(vaultRoot, args, access) {
|
|
|
1156
1129
|
const baseVersion = readBaseVersion(args, "vault_set_tier");
|
|
1157
1130
|
if (!baseVersion.ok)
|
|
1158
1131
|
return baseVersion;
|
|
1159
|
-
const
|
|
1160
|
-
if (!
|
|
1161
|
-
return
|
|
1162
|
-
const
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
const parsed = parseDocument(existing.value);
|
|
1167
|
-
if (!parsed.ok)
|
|
1168
|
-
return parsed;
|
|
1169
|
-
const config = loadConfig(vaultRoot);
|
|
1170
|
-
if (!config.ok)
|
|
1171
|
-
return config;
|
|
1172
|
-
const extensions = config.value.schemaExtensions;
|
|
1173
|
-
const oldFrontmatter = parsed.value.frontmatter;
|
|
1174
|
-
if (access) {
|
|
1175
|
-
const collection = collectionOf(path.value, oldFrontmatter);
|
|
1176
|
-
if (!canWrite(access.role, collection)) {
|
|
1177
|
-
return err(new Error(`access denied: role '${access.roleName}' cannot write to ` +
|
|
1178
|
-
`collection '${collection}'`));
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1132
|
+
const target = await loadTargetDocument(vaultRoot, path.value, "vault_set_tier");
|
|
1133
|
+
if (!target.ok)
|
|
1134
|
+
return target;
|
|
1135
|
+
const oldFrontmatter = target.value.parsed.frontmatter;
|
|
1136
|
+
const rbac = requireWriteAccess(access, collectionOf(path.value, oldFrontmatter));
|
|
1137
|
+
if (!rbac.ok)
|
|
1138
|
+
return rbac;
|
|
1181
1139
|
// The manual consent boundary: only a human identity may lift `manual`.
|
|
1182
1140
|
if (oldFrontmatter.tier === "manual" &&
|
|
1183
1141
|
newTier !== "manual" &&
|
|
@@ -1188,41 +1146,29 @@ export async function vaultSetTier(vaultRoot, args, access) {
|
|
|
1188
1146
|
// No-op guard, same shape as vault_set_confidence: compare against the raw
|
|
1189
1147
|
// on-disk value so an invalid raw tier (validated down to null) can still be
|
|
1190
1148
|
// set to any member.
|
|
1191
|
-
const rawTier =
|
|
1149
|
+
const rawTier = target.value.parsed.raw.tier;
|
|
1192
1150
|
const currentTier = typeof rawTier === "string" && TIERS.includes(rawTier)
|
|
1193
1151
|
? rawTier
|
|
1194
1152
|
: undefined;
|
|
1195
1153
|
if (currentTier === newTier) {
|
|
1196
1154
|
return err(new Error(`vault_set_tier: ${path.value} tier is already '${newTier}'`));
|
|
1197
1155
|
}
|
|
1198
|
-
|
|
1199
|
-
...oldFrontmatter,
|
|
1200
|
-
tier: newTier,
|
|
1201
|
-
updated: todayISO(),
|
|
1202
|
-
updated_by: agent.value,
|
|
1203
|
-
};
|
|
1204
|
-
return performWrite({
|
|
1156
|
+
return performFrontmatterWrite({
|
|
1205
1157
|
vaultRoot,
|
|
1206
|
-
|
|
1207
|
-
// relPath (resolved.value.relPath), never the raw caller string: aliased
|
|
1208
|
-
// spellings of one file must contend on one lock (#127/#128).
|
|
1209
|
-
relPath: resolved.value.relPath,
|
|
1210
|
-
absPath: resolved.value.absPath,
|
|
1158
|
+
target: target.value,
|
|
1211
1159
|
agent: agent.value,
|
|
1212
1160
|
tool: "vault_set_tier",
|
|
1213
1161
|
action: "tier-set",
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1162
|
+
newFrontmatter: {
|
|
1163
|
+
...oldFrontmatter,
|
|
1164
|
+
tier: newTier,
|
|
1165
|
+
updated: todayISO(),
|
|
1166
|
+
updated_by: agent.value,
|
|
1167
|
+
},
|
|
1218
1168
|
commitMessage: `vault_set_tier: ${path.value} ${oldFrontmatter.tier ?? "unset"}→${newTier} ` +
|
|
1219
1169
|
`by ${agent.value} — ${reason.value}`,
|
|
1220
|
-
autoCommit: config.value.autoCommit,
|
|
1221
|
-
gitDir: config.value.gitDir,
|
|
1222
1170
|
baseVersion: baseVersion.value,
|
|
1223
|
-
|
|
1224
|
-
principal: access?.user,
|
|
1225
|
-
bodyChanged: false,
|
|
1171
|
+
access,
|
|
1226
1172
|
});
|
|
1227
1173
|
}
|
|
1228
1174
|
// ---------------------------------------------------------------------------
|
|
@@ -1284,42 +1230,33 @@ export async function vaultSupersede(vaultRoot, args, access) {
|
|
|
1284
1230
|
const config = loadConfig(vaultRoot);
|
|
1285
1231
|
if (!config.ok)
|
|
1286
1232
|
return config;
|
|
1287
|
-
const extensions = config.value.schemaExtensions;
|
|
1288
1233
|
const oldFrontmatter = parsed.value.frontmatter;
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
`collection '${collection}'`));
|
|
1294
|
-
}
|
|
1295
|
-
}
|
|
1296
|
-
const newFrontmatter = {
|
|
1297
|
-
...oldFrontmatter,
|
|
1298
|
-
status: "superseded",
|
|
1299
|
-
superseded_by: newPath.value,
|
|
1300
|
-
updated: todayISO(),
|
|
1301
|
-
updated_by: agent.value,
|
|
1302
|
-
};
|
|
1303
|
-
return performWrite({
|
|
1234
|
+
const rbac = requireWriteAccess(access, collectionOf(oldPath.value, oldFrontmatter));
|
|
1235
|
+
if (!rbac.ok)
|
|
1236
|
+
return rbac;
|
|
1237
|
+
return performFrontmatterWrite({
|
|
1304
1238
|
vaultRoot,
|
|
1305
1239
|
// Canonical relPath keys the lock/provenance/commit (#127/#128).
|
|
1306
|
-
|
|
1307
|
-
|
|
1240
|
+
target: {
|
|
1241
|
+
relPath: resolvedOld.value.relPath,
|
|
1242
|
+
absPath: resolvedOld.value.absPath,
|
|
1243
|
+
parsed: parsed.value,
|
|
1244
|
+
config: config.value,
|
|
1245
|
+
},
|
|
1308
1246
|
agent: agent.value,
|
|
1309
1247
|
tool: "vault_supersede",
|
|
1310
1248
|
action: "supersede",
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1249
|
+
newFrontmatter: {
|
|
1250
|
+
...oldFrontmatter,
|
|
1251
|
+
status: "superseded",
|
|
1252
|
+
superseded_by: newPath.value,
|
|
1253
|
+
updated: todayISO(),
|
|
1254
|
+
updated_by: agent.value,
|
|
1255
|
+
},
|
|
1315
1256
|
commitMessage: `vault_supersede: ${oldPath.value} superseded by ${newPath.value} ` +
|
|
1316
1257
|
`by ${agent.value}${reason ? ` — ${reason}` : ""}`,
|
|
1317
|
-
autoCommit: config.value.autoCommit,
|
|
1318
|
-
gitDir: config.value.gitDir,
|
|
1319
1258
|
baseVersion: baseVersion.value,
|
|
1320
|
-
|
|
1321
|
-
principal: access?.user,
|
|
1322
|
-
bodyChanged: false,
|
|
1259
|
+
access,
|
|
1323
1260
|
});
|
|
1324
1261
|
}
|
|
1325
1262
|
// Combines two source documents into a target and supersedes both sources to
|
|
@@ -1413,10 +1350,9 @@ export async function vaultMerge(vaultRoot, args, access) {
|
|
|
1413
1350
|
targetCollection(vaultRoot, targetPath.value),
|
|
1414
1351
|
];
|
|
1415
1352
|
for (const collection of collections) {
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
}
|
|
1353
|
+
const gate = requireWriteAccess(access, collection);
|
|
1354
|
+
if (!gate.ok)
|
|
1355
|
+
return gate;
|
|
1420
1356
|
}
|
|
1421
1357
|
}
|
|
1422
1358
|
// Target frontmatter: inherit path_a's raw frontmatter, apply caller
|