Commit Graph

2 Commits

Author SHA1 Message Date
Jakub Zych
a9a7a328e6 refactor: hard-fork GSD -> MSD (Make Software Done)
Mechanical rename produced by scripts/msd-rename.cjs: gsd/Gsd/GSD -> msd/Msd/MSD
across contents and paths, upstream package/repo coordinates -> @golem15/msd-core
and golem15com/msd-core. Deep links into upstream history, sibling upstream
packages, the GSD-2 import feature, CHANGELOG.md and .changeset/ are kept as-is.

Hand edits on top: MSD block-letter banner and logos, LICENSE copyright line,
package/plugin identity, regenerated lockfile, install-tree fixtures, derived
registries and benchmark baseline; migration checksum baseline re-locked
(MSD keeps its own install state, so no install had applied the old sums);
sort-order and regex-escaped expectations in tests adjusted.
2026-10-06 01:47:40 +02:00
Tom Boucher
8fa2c3dbcf docs(#4650): record the path-containment and filename-classification design lock (#4655)
Phase 0 of epic #4636. ADR-4650 fixes the decisions Phases 1-4 inherit, so that
four phases do not each invent them independently.

The load-bearing decision is that the engine and the exported shape are
separable. A resolver-based, symlink-safe containment predicate already exists
as validatePath, and building the epic's literal assertWithinRoot() from scratch
would create a sixth implementation of the very thing this epic consolidates --
while risking silent loss of behavior validatePath acquired as bug fixes (a
closed dangling-symlink existence oracle, ancestor canonicalization for
non-canonical roots, a separator-aware boundary test).

But the epic's other clause is correct and lands on the current export:
validatePath returns a boolean a caller can forget to check, and populates
`resolved` with the escaping path precisely on the traversal branch. While that
form stays exported, the Phase-4 ratchet could only assert that a helper was
called -- validatePath(x, root).resolved would pass the rule.

So: preserve the engine, narrow the export. assertWithinRoot becomes the only
export and yields a branded ContainedPath.

Also recorded, each found by measurement rather than from the epic text:

- The rejection message text is a real contract. tests/quick-batch.test.cjs
  asserts a user-facing `reason` field matches /escapes allowed directory/, so
  the string reaches CLI consumers and Phase 3 must preserve it verbatim.
- Two further unconfined boundaries the epic does not enumerate: resolvePath
  and gap-analysis.plan-post, both in check-command-router.cts.
- --phase-dir also interpolates into ${PHASE_DIR} for command-exit-zero, so
  confining at the boundary covers both predicate kinds; the evaluator stays
  fs-free.
- opts.allowAbsolute is a per-call-site liberality knob, which is an acceptance
  policy living exactly where this ADR says it must not.
- planning-inspect's isWithinRoot is deliberately pure-string with no I/O; its
  contract differs, so Phase 3 decides rather than assumes.

The acceptance policy is stated once: conservative about the resource, exact
about the classification. #4580's guard was not too strict, it was wrong -- a
category error comparing a whole suffix against a set of final extensions.

ADR opens as Proposed; ratified at Phase 4 closeout per docs/adr/README.md.
No changeset: the diff touches docs/adr/ only, which is outside
USER_FACING_PREFIXES in scripts/changeset/lint.cjs.

Co-authored-by: sim <sim@local>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 23:45:04 -04:00