Round 2 candidate 7a585aa9606863a0d44a26094b585160914e3d8fd5f13436b56470cd69e6d867
(Rocko, packet fafee2f22), approved by Filbert in comment 28707.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Project record repository — layout 1
A project's records live in a separate local git repository. This is distinct
from its code checkout (business.projects[project].root) and the working bare
repository used for code review. No remote is created or contacted.
Set an optional normalized absolute repo in the business project entry:
{"projects":{"stack":{"root":"/work/code","repo":"/work/project-records"}}}
Without repo, records live at
<dataRoot>/projects/<business>/<project>/. The code and records paths must not
overlap. Record paths and their existing ancestors must not be symlinks.
mosaic project init acme stack
mosaic project check acme stack
Both commands use the existing system and business config loaders. Init requires
an outside-agent CLI environment, a declared project and an empty directory. It
creates main and the first commit as the business's human, with a local
<human>@mosaic.invalid email. Check is read-only; it exits 2 and lists all
problems found. Usage exits 4; configuration problems exit 3. No bus is needed.
Layout
| Path | Contents |
|---|---|
project.json |
layoutVersion: 1, business, project, name, createdAt |
spec/north-star.md |
Human-editable Markdown north star |
docs/ |
Markdown documentation; check accepts nested docs |
requirements/ID.json |
One requirement per file |
decisions/ID.json |
One decision per file |
templates/ |
project.json, requirement.json, decision.json, document.md, north-star.md |
Empty requirements and decisions directories contain an empty .gitkeep, so the
layout survives git checkout. That marker is checked separately from records.
Init makes no example requirements or fictional decisions. The initial project
name is its project ID; the user can edit the display name in project.json.
A requirement has recordVersion: 1, id, title, text, and status
(draft, accepted, complete). A decision has recordVersion: 1, id,
question, options (2–9 distinct {key,text} entries), recommendation,
status (open, accepted, rejected) and choice (null while open, an option
key when closed). These are local reference records, not bus authorization or
approval. Decision spec citations and settlement policy belong to row 138.
Schemas are fixed in packages/project/schemas/, never loaded from the project.
Every JSON template validates against its record schema. Editing templates
cannot weaken validation; adding a project-local schema is refused. The schema
validator implements a closed subset of JSON Schema 2020-12 matching these
schemas and refuses any unsupported schema keyword. Dates use canonical
millisecond UTC. Files are bounded to 1 MiB, valid UTF-8 without NULs, regular,
and not hard-linked. JSON record names must equal their IDs. Unknown top-level
paths, missing required paths, unexpected templates and non-Markdown docs refuse.
The git directory is inspected separately, without reading project data as git
configuration or running hooks.
Shared APIs
Import from packages/project/src/index.mjs:
const context = { business, project: 'stack', dataRoot };
projectRepo(context); // absolute repo path; no writes
await initProject(context); // {repo, commit}
await checkProject(context); // {ok, repo, problems}
validateRecord('requirement', value); // array of problem strings
await writeRecord({
...context,
path: 'requirements/REQ-PROJECT-1.json',
record: value,
author: { definition: 'coder', instance: 'builder' },
}); // {repo, path, commit}
writeRecord is the single agent record writer. It accepts docs/NAME.md,
requirements/ID.json and decisions/ID.json, validates first, and makes a
local commit authored and committed as <definition>.<instance> with a local
.invalid email. Actor metadata is trusted caller input whose shape is
validated here; row 138's broker must derive it from the bound role. This
package neither authenticates an agent nor grants it authority.
Every agent path under spec/ refuses with project-spec-human-only. Agents
cannot write project.json, templates or git metadata through this API. A valid
but unchanged record refuses project-record-unchanged without an empty commit.
Stricter safeguards agreed with Sage (sage-0400): a remote refuses
project-remote; pending tracked, staged, untracked or ignored files and hidden
index flags refuse project-dirty. The writer never folds human edits into its
commit. Concurrent writers refuse project-busy through an exclusive local
lock; no stale lock is automatically removed. Links, indirect git stores and
unsupported local git config refuse. Git uses no inherited global/system
configuration, no hooks, fsmonitor, filters or transports, and runs in bounded
process groups using the existing seat git runner.
Records publish with a temporary file, fsync and rename, then a commit of only
that path. On git failure with HEAD unchanged, only the writer's file/index
change is restored. If HEAD changed while an error occurred, the writer refuses
project-commit-uncertain and preserves evidence for reconciliation; it never
rewinds history. Locking serializes this writer, not arbitrary human editors:
callers must coordinate simultaneous human saves. No destructive repair, push,
remote creation, role grant, spec-confirmation mechanism or live action ships.
Tests: node --test packages/project/tests/*.test.mjs. Fixtures only.