Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions docs/design/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ An agent is not a consumer with novel requirements. It is one that exhibits, con

So the agent question is what makes a property visible, and the property is what gets specified. Keep both: the question is the best generator of design problems this project has, and skipping the translation is what produces bad scope. Asked directly, "what does an agent need" answers with surfaces — an inspector, an event stream, a machine-readable everything. Asked as a property, the same need answers with an invariant, and invariants are the things that cannot be added later for free.

Hence the test for anything proposed on some consumer's behalf: does it survive translation to the others? `--expect-generation N` does — a person doing a risky activation twice a year wants it for the same reason an agent does, and the reason is that neither can be sure the state they observed is still there. A command that prints existing state as JSON does not; it is a convenience for one caller, genuinely useful, and additive. The first is deadline-bearing and the second is not.
Hence the test for anything proposed on some consumer's behalf: does it survive translation to the others? A command that prints existing state as JSON does not; it is a convenience for one caller, genuinely useful, and additive. Even an invariant that survives the translation still needs a concrete use: conditional activation, for example, is only meaningful when a caller acts on an earlier observation rather than asking Mere to perform the whole operation at once.

One place the frame runs out, recorded so it is not forced: several consumers operating on one root at once is a scale difference that becomes a kind difference, and no single-caller property naturalizes it. That is why lockless store admission is scheduled on its own terms below rather than derived from this test.

Expand All @@ -43,7 +43,6 @@ Three kinds of work, distinguished by whether they have a deadline.
Format changes (detailed below):

- Decide what store identity covers
- Rollback protection for repository metadata
- Signature discriminator and domain separation
- Retire read-only variants
- Settle the store's permission model
Expand All @@ -56,7 +55,6 @@ Capability removals:

Closing the remaining conformance gaps:

- **`--expect-generation N` on activation.** Closes the half of the concurrency gap that consumers actually need: expressing "apply only if the profile is still where I last saw it", so a loser re-derives instead of clobbering. Small — activation already stages a temporary symlink and renames it.
- **Attestation.** A signed, append-only record of what was done, closing "no record of action". Self-contained, and reuses the existing Ed25519 machinery.
- **Artifact schemas: accepted, not closed.** Machine-readable schemas for the hand-rolled binary formats are deliberately *not* planned. Consumers reach these formats through Mere rather than around it, the shapes are already specified in `specification-details.md`, and a hand-maintained schema document would carry exactly the drift risk that `mere describe` exists to avoid. What "through Mere" owes depends on the artifact, and the cases are not alike. A generation's `profile.kdl` (§6) is KDL — already legible, already the declarative input to the operation that produced it — and nothing is owed for it. The binary encodings are where the question arises, and being binary does not settle it either: a package manifest (§17) holds the authoritative package metadata, and a consumer asking what a package is has no other path to it, so declining the schema is what commits Mere to a reader. A realization manifest is equally opaque but answers a file-level question that so far only Mere and its own garbage collection ask. So the test has two steps and an order: an artifact that is already legible owes nothing, and a binary one owes a reader where a consumer needs the question it answers.

Expand All @@ -65,7 +63,7 @@ Closing the remaining conformance gaps:
- **Plans as artifacts.** Additive alongside the existing imperative commands.
- **Machine-readable output.** The commands that answer inspection questions already exist — `store verify`, `profile list`, `generation list`, `status`, `search`, `etc status`, `etc diff`, `key fingerprint`. What they lack is a serialization a consumer can parse without scraping prose. That is an output mode on commands that already answer the question, not a second family of `inspect` commands standing beside them, and it freezes no representation the human output has not already frozen.
- **Failure identity in machine output.** The vocabulary exists and so does the exit-code mapping; what a consumer cannot currently see is which member of the vocabulary it hit. See "Exit status" in the contract for the distinction the present codes lose.
- **Lockless store admission.** The other half of the concurrency gap: a content-addressed store needs no mutual exclusion, so unrelated work should stop serializing on one lock per root. Deferred deliberately — its failure mode is corrupted state rather than an error message, and the suite cannot exercise concurrency at all today. The prerequisite is test scaffolding that runs concurrent mutators, which is hard to bound before starting it. Better in a 1.1 with real tests than rushed to make a date.
- **Lockless store admission.** A content-addressed store needs no mutual exclusion, so unrelated work should stop serializing on one lock per root. Deferred deliberately — its failure mode is corrupted state rather than an error message, and the suite cannot exercise concurrency at all today. The prerequisite is test scaffolding that runs concurrent mutators, which is hard to bound before starting it. Better in a 1.1 with real tests than rushed to make a date.

## Detail

Expand All @@ -74,7 +72,6 @@ Closing the remaining conformance gaps:
None of these is impossible after 1.0. Each done later means another accepted variant, carried by every verification path until a major version retires it.

- **Decide what store identity covers.** The content hash currently records a file's exec bit and nothing else about mode: setuid, setgid, sticky, the remaining permission bits, and ownership are all outside it, so two payloads differing only in setuid share a store path and `mere store verify` cannot tell them apart. Changing this changes every content hash. Doing it before 1.0 costs a v3 variant; doing it after costs a v3 variant *and* a fourth fallback.
- **Decide on rollback protection for repository metadata.** A validly signed but older `repo.db` is currently indistinguishable from the current one, so a mirror can pin a client to a stale snapshot and keep offering known-vulnerable versions. A monotonic sequence number in the signed database would close this without timestamps or clock trust, which matters because "no network trust or timestamps" is an explicit non-goal. This is a `repo.db` schema change.
- **Give signatures a discriminator.** A manifest signature is 64 raw bytes with no header, so there is no way to express a different algorithm — even though the key file it verifies against records one. Separately, signatures are computed over a digest in one path and over raw bytes in another, with no domain separation between them.
- **Retire read-only variants.** The transitional store hash exists for one unversioned release and is carried by five fallback sites. Decide whether v1 store objects are still readable at 1.0, and drop what is not.
- **Settle the store's permission model.** The store is world-writable by design (§4.1, two-tier admission), and the existing-object fast path accepts a directory on the strength of its content-addressed name. Those two decisions interact, and the interaction should be deliberate.
Expand Down Expand Up @@ -109,7 +106,7 @@ Existing generations have no `requested`, so everything in them reads as request
- **Remove ambient state.** Working-directory profile discovery and TTL-based metadata staleness both make one command mean different things on different runs for reasons not visible in the command. Where the convenience is worth keeping for interactive use, it should at least be resolvable to an explicit form.
- **Attestation.** A signed, append-only record of what was done — who, which plan, which generation before and after.
- **Plans as artifacts.** Separate deciding from doing: resolve an operation into a content-addressed plan, then apply the plan by its hash. Gives idempotent retry, a reviewable diff before anything mutates, and a natural identity for crash recovery.
- **Concurrency.** Two halves, scheduled apart. `--expect-generation N` is small and belongs before 1.0. Dropping the per-root lock for store admission is not, and waits for concurrent-mutator tests.
- **Concurrency.** Dropping the per-root lock for store admission waits for concurrent-mutator tests. Conditional activation is not scheduled: without a concrete split between observing state and applying a previously derived decision, an expected-generation argument merely asks callers to repeat state Mere can already read. Reconsider it if a real split-phase consumer or observed lost update establishes the need.
- **Hermetic builds.** Builds currently have network access and inherit the host resolver and CA bundle, so a build is not a function of its declared inputs.

## Release approach
Expand All @@ -124,4 +121,5 @@ Existing generations have no `requested`, so everything in them reads as request

Recorded so they are not mistaken for oversights:

- **Repository metadata freshness is not inferred.** A repository signature authenticates provenance and integrity, not universal recency. Mere does not retain a highest-seen timestamp or sequence: timestamps require clock authority, while counters still require every mirror, restored machine, and recovery path to agree on one canonical lineage. Without that authority, anti-rollback state can reject legitimate metadata and make recovery or intentional rollback unsafe. A hostile mirror can therefore replay an older validly signed database; deployments that require freshness need a separately trusted distribution mechanism. Revisit this only with a concrete repository, replica, and recovery model.
- Namespace setup is not covered by the test suite: the suite substitutes a host runner, so `enterEnv` never executes under test. Verification is manual. An integration test that actually enters a namespace would close this, and is a prerequisite worth having before the hermetic-build work changes that setup again.