Each registered Product can hold core values: 0–6 ordered values, each a short statement and an application, the concrete rule for choosing between development options. Source: src/project-guidance.ts. Edited in the dashboard at /projects/:id/values.
What guidance is. Values are the owner's stated intent for a Product. They are locally saved guidance, not calibrated doctrine. They grant no delivery Authority, and they never override acceptance, budget or safety constraints. They are never copied between Products, and they write nothing into a Product's files. An empty or cleared list is not a gate, and removes no existing constraint.
What guidance is not yet. Nothing in the factory reads values automatically. No planner, brief builder or provider consumes them. The contract below is ready for a future brief to pin; this increment demonstrates only that pins stay stable and reviewable.
Storage
- Aggregate.
guidance:product:<productId>in the Portfolio journal, written only throughCommandJournal.execute. The aggregate version is the revision. - Revisions. Revision 0 means unset. Every accepted command records exactly one new revision, including one that clears the list. Each revision is one
guidance.values-setevent, and history is never rewritten. - Bounds. At most 500 revisions per Product (
LIMITbeyond that). Each text field is 1–280 printable single-line characters. The canonical payload is at most 16 KiB. - Value IDs.
[a-z0-9][a-z0-9-]{0,39}, unique within a revision. An ID stays the same when the value is reworded or reordered. - Registration. The Product must be registered. Guidance never reads the checkout, so a missing or unreadable repository does not block editing.
Provenance
Each revision records where its values came from, and the digest includes it:
origin | Meaning | note |
|---|---|---|
direct-entry | Typed by a person. Every dashboard edit is recorded this way; the HTTP API refuses any other value. | Optional |
document-summary | Summarised from a document, not entered directly | Required: names the document |
Provenance records the origin of the input. It says nothing about identity or authority. A summary does not become calibrated doctrine by being saved.
API
const guidance = new ProductGuidance(journal, { now? });
guidance.setValues({ commandId, productId, expectedRevision, values, provenance? }): SetValuesResult
guidance.current(productId): GuidanceSnapshot
guidance.revision(productId, revision): GuidanceSnapshot
guidance.history(productId, { before?, limit? }): GuidanceHistory // newest first, 1-20 per page
guidance.resolvePin({ productId, revision, digest }): GuidanceSnapshot
guidanceDigest(productId, values, provenance): string
A GuidanceSnapshot is { protocol: "product-guidance/1", productId, revision, digest, recordedAt, provenance, values }, deeply frozen. digest is SHA-256 over canonical { protocol, productId, values, provenance }. It excludes the revision number, so a pin carries both.
Commands
| Situation | Result |
|---|---|
| New command ID at the current revision | A new revision |
| Same ID, same Product, expected revision, values and provenance | The recorded result with replayed: true, even after later revisions. Nothing is written. |
| Same ID with anything different | CONFLICT |
expectedRevision is not current | STALE, with currentRevision. Nothing is merged, rebased or retried. |
| Invalid input or an unregistered Product | INVALID or NOT_FOUND, and nothing is written |
Identical content under a new command ID is still a new revision; no content shortcut bypasses command receipts.
Integrity
Every read re-validates each event's digest and checks that the aggregate's state is exactly its last recorded revision. Every result is checked against its request, and a replayed result against the revision it recorded. A mismatch is CORRUPT for that Product only: nothing is returned as a valid snapshot or pin, and no edit writes over it.
For future briefs
A brief that uses guidance should:
- pin
{ productId, revision, digest }fromcurrent(); - present the applicable values'
applicationrules; - explain any material trade-off made against them.
Resolve the pin with resolvePin. A later edit never changes what an existing pin resolves to; new planning uses the new revision. A pin whose digest does not match is refused (CONFLICT), never upgraded. Values cannot widen authority; dispatch eligibility belongs to the later Product gate.
Tests
| File | Covers |
|---|---|
test/project-guidance.test.ts | Restart and history; rewording and reordering keep IDs; clearing is a revision; replay after later edits; changed-command conflict; stale writes nothing; two real processes at one revision; invalid, oversized, unregistered and cross-Product input writes nothing; unavailable checkout; pin stability; provenance; state, history and receipt disagreement is CORRUPT and never overwritten |
test/dashboard-guidance-attention.test.ts | The HTTP command: guards, bounds, restart and replay, STALE and CONFLICT, direct-entry provenance |
test/dashboard-editor.test.ts | The editor keeps one operation ID for a retry, mints a new one for any change, classifies failures and describes conflicts |
