Skip to main content

When to Use

When publishing a prompt unit via stratt publish. This is the most complex CLI command — a 5-step saga with failure compensation.

The Saga

Step 1: Validate          → exit 1 on failure (safe — nothing written)
Step 2: Compute fingerprint → write to file via parseDocument() (lossless)
Step 3: Dry-run check      → if --dry-run, revert file write and exit 0
Step 4: Upload to R2       → if fails, revert file write, exit 4 (safe — nothing published)
Step 5: Git commit         → if fails, warn (R2 already correct — lesser evil)

Why R2 First (FM-09)

OrderingR2 FailsGit Fails
R2-first-then-Git (correct)Revert fingerprint, exit 4 — clean stateWarn — R2 has correct content, Git can be fixed manually
Git-first-then-R2 (wrong)Git has fingerprint pointing to nothing — orphaned stateN/A
R2-first means the external canonical state is always correct. Git failures are recoverable with git add + git commit.

YAML Round-Trip Fidelity

The fingerprint write uses yaml.parseDocument()doc.set("fingerprint", value)doc.toString(). This preserves:
  • Comments
  • Scalar styles (quoted vs plain)
  • Key ordering
  • Whitespace formatting
Using yaml.parse() → modify → yaml.stringify() would rewrite the entire file, creating noisy diffs.

Compensation

const doc = await readUnitDocument(path);
const originalContent = doc.toString();
doc.set("fingerprint", fp.full);
await Bun.write(path, doc.toString());

try {
  await r2.put(key, await readRawYaml(path));
} catch (err) {
  // Compensate: restore original content
  await Bun.write(path, originalContent);
  process.exit(EXIT.R2_ERROR);
}
The original content is captured before the fingerprint write. On R2 failure, it is restored byte-for-byte.

R2 Key Derivation

strat://dev/task/extract-logs@1.0.0  →  units/dev/task/extract-logs@1.0.0.yaml
Simple string transformation: strip strat://, prepend units/, append .yaml.