docs: the region HA contracts note records expectSeq, the fenced-source condition for copy retries, and Publish's refusal of replaced and torn files
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 file changed,
+33
−8
docs/superpowers/notes/2026-10-07-region-ha-contracts.md+33 −8
CI passed — run details
diff --git a/docs/superpowers/notes/2026-10-07-region-ha-contracts.md b/docs/superpowers/notes/2026-10-07-region-ha-contracts.md| index e06ceb98d..a5acba97f 100644 |
| --- a/docs/superpowers/notes/2026-10-07-region-ha-contracts.md |
| +++ b/docs/superpowers/notes/2026-10-07-region-ha-contracts.md |
| @@ -28,11 +28,16 @@ Signatures: |
| func Restore(ctx context.Context, s release.Store, prefix, dir string, origins []string) (Manifest, RestoreStats, error) |
| func ReadPlatform(ctx context.Context, s release.Store, prefix string, m Manifest, name string, origins []string) ([]byte, error) |
| func LoadWake(ctx context.Context, s release.Store, prefix string, m Manifest, origins []string) (WakeState, bool, error) |
| -func CopyManifest(ctx context.Context, s release.Store, fromPrefix, toPrefix string, origins []string) (Manifest, error) |
| +func CopyManifest(ctx context.Context, s release.Store, fromPrefix, toPrefix string, expectSeq int64, origins []string) (Manifest, error) |
| var ErrOrigin = errors.New("hostfiles: object origin not admitted") |
| var ErrManifestExists = errors.New("hostfiles: the target generation already has a manifest") |
| +var ErrSourceMoved = errors.New("hostfiles: the source manifest is not at the expected seq") |
| ``` |
| +`prefix` must be a clean relative bucket key ending in `/`. Restore, |
| +ReadPlatform (and so LoadWake), Open and CopyManifest refuse any other |
| +prefix before reading anything. |
| + |
| `origins` is the set of generation prefixes that the caller's pin admits. |
| nil or empty means the manifest's own prefix only. The set is used exactly |
| as passed: a caller that passes a set must include the manifest's own |
| @@ -58,17 +63,32 @@ ReadPlatform and LoadWake apply the same rules to platform items. |
| CopyManifest reads `fromPrefix`'s manifest and validates every entry |
| against `origins` (nil means `fromPrefix` alone). A source built by an |
| earlier copy references earlier generations, so its caller passes the |
| -whole chain. CopyManifest then writes the same bytes, with the same Seq and |
| -the same qualified references, to `toPrefix`'s manifest. The write is |
| -create-only. |
| -No objects are copied. Errors: |
| +whole chain. CopyManifest then writes the same manifest (entries and Seq), |
| +with the same qualified references, to `toPrefix`'s manifest. The write is |
| +create-only. No objects are copied. |
| + |
| +`expectSeq` is the source revision the caller's pin admitted. When it is |
| +not 0 and the source is at any other Seq, the call returns |
| +`ErrSourceMoved` and writes nothing, on a first attempt or a retry. 0 |
| +copies whatever the source holds. |
| + |
| +Errors: |
| - an absent source is `os.ErrNotExist`, never an empty copy; |
| -- a target already holding identical bytes is success, so a retry is safe; |
| +- a source not at a non-zero `expectSeq` is `ErrSourceMoved`; |
| +- a target already holding exactly the manifest this call would write is |
| + success, so a retry is safe; |
| - any other target is `ErrManifestExists`, and the target is left as it |
| was; |
| - a source equal to the target is refused. |
| +Retry-safety requires a source that cannot commit between attempts: the |
| +source generation must be fenced or frozen. The claim's admission provides |
| +this. Against a live source, a retry can find the target holding an older |
| +revision. It then reports `ErrManifestExists`, or `ErrSourceMoved` when |
| +`expectSeq` is set. It never reports success for a revision the caller |
| +did not admit. |
| + |
| The new generation's `Open(toPrefix)` continues the sequence from the |
| copy. An unchanged file keeps its old-generation reference. A changed file |
| becomes a new object under `toPrefix`. |
| @@ -95,8 +115,13 @@ func Publish(ctx context.Context, s release.Store, prefix, dir string, skip func |
| in the manifest. A file larger than `MaxFileBytes` is recorded as |
| skipped and listed in `SyncStats.TooLarge`. Anything that is not a |
| regular file is left out and listed in `SyncStats.NotRegular`. |
| -- A file that exists but cannot be read is an error, and nothing is |
| - committed. |
| +- Publish is an install-time contract: it commits a complete and |
| + consistent image or nothing. It returns an error and commits nothing |
| + when any file could not be read (`SyncStats.Unreadable`), was replaced |
| + between the look and the open (`SyncStats.Retry`), or changed while it |
| + was read (`SyncStats.Torn`). The caller stops writers and calls Publish |
| + again. Objects uploaded by a refused attempt are referenced by no |
| + manifest. |
| - A prefix that already has a manifest is continued, not replaced. A |
| repeat with nothing changed returns the same sequence number. If another |
| writer's manifest appears between Open and Commit, the error is |