Skip to content

Commit 3d4b7da

Browse files
jrcrucianiCopilot
andcommitted
Resolve v3 minimal vault install DX gaps
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 1464623 commit 3d4b7da

2 files changed

Lines changed: 39 additions & 10 deletions

File tree

examples/v3-minimal-vault/README.md

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,34 @@ valid_to: null
100100

101101
Every referenced entity must appear in `memory/entities.md`. This is strict because typos in entity IDs silently split memory.
102102

103-
> **Common mistakes**
104-
>
105-
> - `sources:` entries are local filesystem paths relative to the vault. To cite an external URL, put it in the note body or save a local source file under `sources/articles/` and cite that file.
106-
> - New fact predicates must be declared in `memory/schema/predicates.yaml` before facts use them.
103+
### Common mistakes lint will catch
104+
105+
A short field guide to first-contact errors and how to fix them:
106+
107+
- **`source does not exist: https://...`** — `sources:` entries are local filesystem paths relative to the vault root, not URLs. To cite an external URL, put it in the note body or save a local source file under `sources/articles/` and cite that path.
108+
- **`missing required field 'recorded_at'`** — every `fact` needs an ISO-8601 timestamp for when the fact was recorded. `valid_from` is separate and optional.
109+
110+
```yaml
111+
type: fact
112+
entity: elena-voss
113+
predicate: role
114+
value: "Art conservator"
115+
recorded_at: 2026-05-13T18:51:00Z
116+
valid_from: 2026-05-13
117+
valid_to: null
118+
confidence: high
119+
sources: []
120+
```
121+
122+
- **`unknown predicate 'X'`** — predicates are controlled by `memory/schema/predicates.yaml`. Declare the predicate before writing facts or operation payloads that use it.
123+
124+
```yaml
125+
predicates:
126+
- id: spec-version
127+
description: Declared spec version of a memory vault or schema.
128+
```
129+
130+
- **`source:` vs `sources:`** — the schema field is the plural array `sources: []`, not the singular `source:`.
107131

108132
### Narrative note schemas
109133

examples/v3-minimal-vault/tools/lint.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
HASH_RE = re.compile(r"^sha256:[0-9a-f]{64}$")
2828
SUPPORTED_SPEC_VERSION = "3.0"
2929
PREDICATE_HINT = "add it to memory/schema/predicates.yaml"
30+
SOURCE_HINT = "sources: items must be filesystem paths relative to the vault root, not URLs"
3031

3132

3233
class Finding:
@@ -118,6 +119,10 @@ def unknown_predicate_message(predicate: Any, prefix: str = "unknown predicate")
118119
return f"{prefix} {predicate!r} -- {PREDICATE_HINT}"
119120

120121

122+
def missing_source_message(source: Any, prefix: str = "source does not exist") -> str:
123+
return f"{prefix}: {source} -- {SOURCE_HINT}"
124+
125+
121126
def validate_type(value: Any, expected: Any) -> bool:
122127
if isinstance(expected, list):
123128
return any(validate_type(value, item) for item in expected)
@@ -297,7 +302,7 @@ def validate_operation_payload(
297302
findings.append(Finding("ERROR", path, f"payload: {exc}"))
298303
for source in payload.get("sources", []) or []:
299304
if not (root / source).exists():
300-
findings.append(Finding("ERROR", path, f"payload source does not exist: {source}"))
305+
findings.append(Finding("ERROR", path, missing_source_message(source, "payload source does not exist")))
301306

302307
if op == "add_event":
303308
if payload_type != "event":
@@ -307,7 +312,7 @@ def validate_operation_payload(
307312
findings.append(Finding("ERROR", path, f"payload unknown event entity {entity!r}"))
308313
for source in payload.get("sources", []) or []:
309314
if not (root / source).exists():
310-
findings.append(Finding("ERROR", path, f"payload source does not exist: {source}"))
315+
findings.append(Finding("ERROR", path, missing_source_message(source, "payload source does not exist")))
311316

312317
return findings
313318

@@ -376,7 +381,7 @@ def validate(root: Path) -> list[Finding]:
376381
findings.append(Finding("ERROR", path, f"superseded_by target does not exist: {superseded_by}"))
377382
for source in data.get("sources", []) or []:
378383
if not (root / source).exists():
379-
findings.append(Finding("ERROR", path, f"source does not exist: {source}"))
384+
findings.append(Finding("ERROR", path, missing_source_message(source)))
380385

381386
if typ == "event":
382387
for entity in data.get("entities", []) or []:
@@ -391,7 +396,7 @@ def validate(root: Path) -> list[Finding]:
391396
findings.append(Finding("ERROR", path, str(exc)))
392397
for source in data.get("sources", []) or []:
393398
if not (root / source).exists():
394-
findings.append(Finding("ERROR", path, f"source does not exist: {source}"))
399+
findings.append(Finding("ERROR", path, missing_source_message(source)))
395400
for derived in data.get("derived_facts", []) or []:
396401
if not (root / derived).exists():
397402
findings.append(Finding("ERROR", path, f"derived fact does not exist: {derived}"))
@@ -414,7 +419,7 @@ def validate(root: Path) -> list[Finding]:
414419
findings.append(Finding("ERROR", path, "target_path must be a relative path without '..'"))
415420
for source in data.get("sources", []) or []:
416421
if not (root / source).exists():
417-
findings.append(Finding("ERROR", path, f"source does not exist: {source}"))
422+
findings.append(Finding("ERROR", path, missing_source_message(source)))
418423
findings.extend(validate_operation_payload(root, path, data, schemas, entities, predicates))
419424

420425
if typ in {"decision", "insight"}:
@@ -423,7 +428,7 @@ def validate(root: Path) -> list[Finding]:
423428
findings.append(Finding("ERROR", path, f"unknown {typ} entity {entity!r}"))
424429
for source in data.get("sources", []) or []:
425430
if not (root / source).exists():
426-
findings.append(Finding("ERROR", path, f"source does not exist: {source}"))
431+
findings.append(Finding("ERROR", path, missing_source_message(source)))
427432

428433
if typ == "entity-index":
429434
for item in data.get("entities", []) or []:

0 commit comments

Comments
 (0)