Complete filesystem simplification workstream
This commit is contained in:
@@ -35,7 +35,7 @@ progress and artifact services resolve durable inputs and outputs.
|
||||
| Artifacts and paths | `internal/artifacts`, `internal/pathsafe` | Artifact identities and resolution, local and remote path/key models, current-state discovery, and confined relative destinations. |
|
||||
| Previous-session cache | `internal/previouscache` | Deterministic planning and materialization requirements for configured previous-session inputs. |
|
||||
| Artifact policy | `internal/artifactpolicy` | Source and destination policy, configured artifact identity validation, and publish destination safety. |
|
||||
| Shared models and file operations | `internal/artifactmodel`, `internal/contracts`, [`internal/fileops`](fileops.md) | Transcript and artifact data contracts plus durable single-file replacement helpers; unsupported directory syncing is reported explicitly. |
|
||||
| Shared models and file operations | `internal/artifactmodel`, `internal/contracts`, [`internal/fileops`](fileops.md) | Transcript and artifact data contracts plus atomic single-file replacement, bounded reads, workspace modes, and root-scoped cleanup helpers. |
|
||||
| Logging | `internal/logging` | Application logger construction and shared structured logging behavior. |
|
||||
|
||||
The application boundary composes concrete implementations. Stages depend on
|
||||
|
||||
@@ -46,8 +46,8 @@ invocation-local snapshot path, never payload contents. Snapshot bytes must
|
||||
match the prepared identity both before and after Notarius runs, so a concurrent
|
||||
prepared-file replacement cannot make recorded provenance describe different
|
||||
bytes from those supplied to Notarius.
|
||||
Validation completes before
|
||||
promotion, so a rejected result cannot expose a partial durable bundle.
|
||||
Validation completes before the single durable rename, so a rejected result
|
||||
cannot expose a partial durable bundle.
|
||||
|
||||
Any executed extraction outcome that replaces a different effective outcome
|
||||
marks succeeded analysis and delivery dependents stale. Render is an independent
|
||||
@@ -92,11 +92,11 @@ identifier.
|
||||
Adapter startup, timeout, nonzero exit, receipt decoding, path confinement,
|
||||
index compatibility, inconsistent warning or diagnostic envelopes,
|
||||
required-lane rejection or incomplete validation, payload inspection,
|
||||
checksum, or promotion errors fail the stage through ordinary manifest
|
||||
checksum, or installation errors fail the stage through ordinary manifest
|
||||
transition handling.
|
||||
Stdout receipt and stderr diagnostics remain separate. Downstream stages are
|
||||
not given selectable extraction sources unless the complete configured result
|
||||
has passed validation and promotion.
|
||||
has passed validation and installation.
|
||||
|
||||
When a replacement attempt begins, the current session-stage record no longer
|
||||
advertises payload from the previous success. A failed replacement therefore
|
||||
|
||||
@@ -33,13 +33,8 @@ previous-session cache paths remain session-durable and are never redirected
|
||||
into run-local output space.
|
||||
|
||||
Extraction uses run-local receipt, stderr, and output-root helpers, then
|
||||
promotes the validated external bundle to the unique immutable Notarius bundle
|
||||
path supplied by `internal/artifacts`. `internal/fileops.PromoteDirectory`
|
||||
copies only regular files and directories to a same-filesystem temporary
|
||||
sibling. Source traversal uses confined directory handles and identity checks
|
||||
so replacing an inspected root, directory, or file is rejected rather than
|
||||
followed. The completed tree is atomically renamed without replacing an
|
||||
existing destination. Exact physical paths belong in
|
||||
validates and renames the external bundle to the unique immutable Notarius
|
||||
bundle path supplied by `internal/artifacts`. Exact physical paths belong in
|
||||
[Operations](../operations.md#extraction-workflow).
|
||||
|
||||
## Locking
|
||||
@@ -84,12 +79,12 @@ syntax and exact deletion scope belong in [CLI](../cli.md#clean) and
|
||||
- Path model and local store: `internal/artifacts/paths.go`,
|
||||
`internal/artifacts/local.go`
|
||||
- Run-local materialization: `internal/stage/run_local.go`
|
||||
- Immutable bundle promotion: `internal/fileops/directory.go`
|
||||
- Immutable bundle installation: `internal/stage/extract.go`
|
||||
- Workspace modes: `internal/fileops/modes.go`
|
||||
- Cleanup confinement: `internal/fileops/cleanup.go`,
|
||||
`internal/app/cleanup_targets.go`, `internal/app/post_publish_cleanup.go`
|
||||
- Tests: `internal/artifacts/paths_model_test.go`,
|
||||
`internal/artifacts/local_test.go`, `internal/stage/run_local_test.go`,
|
||||
`internal/fileops/directory_test.go`, `internal/fileops/modes_posix_test.go`,
|
||||
`internal/fileops/modes_posix_test.go`,
|
||||
`internal/fileops/cleanup_test.go`, `internal/app/cleanup_targets_test.go`,
|
||||
`internal/app/post_publish_cleanup_test.go`
|
||||
|
||||
@@ -604,7 +604,7 @@ directory-promotion machinery.
|
||||
|
||||
## Stage 8 — Finish The Filesystem Contract And Validate The Workstream
|
||||
|
||||
**Status: Pending**
|
||||
**Status: Completed**
|
||||
|
||||
### Goal
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ new external subprocess library during this refactor.
|
||||
|
||||
### Workstream 2 — Filesystem simplification and restore correctness
|
||||
|
||||
**Status:** Pending
|
||||
**Status:** Complete
|
||||
|
||||
**Audit sources:** H02, H06, H07, H18, H20, H21, H29, H45, H46, C04,
|
||||
L02, L05a, and L05c. Supporting filesystem rationale and policy are recorded
|
||||
|
||||
@@ -12,13 +12,9 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ErrDirectorySyncUnsupported is used only by the retained directory-promotion
|
||||
// implementation. Single-file replacement does not perform directory syncing.
|
||||
var ErrDirectorySyncUnsupported = errors.New("directory sync is unsupported")
|
||||
|
||||
// ReplaceFileOptions describes the caller-owned parts of a byte-file
|
||||
// replacement. The destination parent must already exist. BeforeRename runs
|
||||
// after the temporary file is complete and durable, but before replacement is
|
||||
// after the temporary file is complete, but before replacement is
|
||||
// committed; callers can use it for cancellation or other commit checks.
|
||||
type ReplaceFileOptions struct {
|
||||
Mode os.FileMode
|
||||
@@ -32,7 +28,7 @@ func ReplaceFileAtomic(dst string, data []byte, options ReplaceFileOptions) erro
|
||||
}
|
||||
|
||||
// WriteFileAtomic creates the destination parent with workspace permissions
|
||||
// and durably replaces dst with data. Callers that own parent-directory policy
|
||||
// and atomically replaces dst with data. Callers that own parent-directory policy
|
||||
// or need a pre-commit check should use ReplaceFileAtomic directly.
|
||||
func WriteFileAtomic(dst string, data []byte, perm os.FileMode) error {
|
||||
if strings.TrimSpace(dst) == "" {
|
||||
@@ -44,13 +40,13 @@ func WriteFileAtomic(dst string, data []byte, perm os.FileMode) error {
|
||||
return ReplaceFileAtomic(dst, data, ReplaceFileOptions{Mode: perm})
|
||||
}
|
||||
|
||||
// CopyFileAtomic copies src to dst through the durable replacement sequence.
|
||||
// CopyFileAtomic copies src to dst through the replacement sequence.
|
||||
func CopyFileAtomic(src, dst string, perm os.FileMode) error {
|
||||
_, err := CopyFileAtomicWithChecksum(src, dst, perm)
|
||||
return err
|
||||
}
|
||||
|
||||
// CopyFileAtomicWithChecksum copies src to dst through the durable replacement
|
||||
// CopyFileAtomicWithChecksum copies src to dst through the replacement
|
||||
// sequence and returns the SHA-256 checksum of the copied bytes.
|
||||
func CopyFileAtomicWithChecksum(src, dst string, perm os.FileMode) (string, error) {
|
||||
if strings.TrimSpace(src) == "" || strings.TrimSpace(dst) == "" {
|
||||
@@ -66,7 +62,7 @@ func CopyFileAtomicWithChecksum(src, dst string, perm os.FileMode) (string, erro
|
||||
return WriteReaderAtomicWithChecksum(dst, in, perm)
|
||||
}
|
||||
|
||||
// WriteReaderAtomicWithChecksum streams src through the durable replacement
|
||||
// WriteReaderAtomicWithChecksum streams src through the replacement
|
||||
// sequence and returns the SHA-256 checksum of the installed bytes. The caller
|
||||
// retains ownership of src.
|
||||
func WriteReaderAtomicWithChecksum(dst string, src io.Reader, perm os.FileMode) (string, error) {
|
||||
|
||||
@@ -103,7 +103,7 @@ func TestAssetBuilderBuildsNamedAssetsAndChecksEmbeddedVersion(t *testing.T) {
|
||||
outputDir := filepath.Join(t.TempDir(), "assets")
|
||||
goLog := filepath.Join(t.TempDir(), "go.log")
|
||||
|
||||
command := exec.Command("sh", builder, "v1.2.3", outputDir)
|
||||
command := exec.Command("sh", builder, "v0.0.0-test", outputDir)
|
||||
command.Dir = t.TempDir()
|
||||
command.Env = append(os.Environ(),
|
||||
"PATH="+binDir+string(os.PathListSeparator)+os.Getenv("PATH"),
|
||||
@@ -124,23 +124,23 @@ func TestAssetBuilderBuildsNamedAssetsAndChecksEmbeddedVersion(t *testing.T) {
|
||||
}
|
||||
sort.Strings(got)
|
||||
want := []string{
|
||||
"narratio-v1.2.3-darwin-amd64",
|
||||
"narratio-v1.2.3-darwin-arm64",
|
||||
"narratio-v1.2.3-linux-amd64",
|
||||
"narratio-v1.2.3-linux-arm64",
|
||||
"narratio-v1.2.3-windows-amd64.exe",
|
||||
"narratio-v1.2.3-windows-arm64.exe",
|
||||
"narratio-v0.0.0-test-darwin-amd64",
|
||||
"narratio-v0.0.0-test-darwin-arm64",
|
||||
"narratio-v0.0.0-test-linux-amd64",
|
||||
"narratio-v0.0.0-test-linux-arm64",
|
||||
"narratio-v0.0.0-test-windows-amd64.exe",
|
||||
"narratio-v0.0.0-test-windows-arm64.exe",
|
||||
}
|
||||
if !slicesEqual(got, want) {
|
||||
t.Fatalf("asset names = %#v, want %#v", got, want)
|
||||
}
|
||||
version := exec.Command(filepath.Join(outputDir, "narratio-v1.2.3-linux-amd64"), "version")
|
||||
version := exec.Command(filepath.Join(outputDir, "narratio-v0.0.0-test-linux-amd64"), "version")
|
||||
var versionOutput bytes.Buffer
|
||||
version.Stdout = &versionOutput
|
||||
if err := version.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := versionOutput.String(); got != "narratio v1.2.3\n" {
|
||||
if got := versionOutput.String(); got != "narratio v0.0.0-test\n" {
|
||||
t.Fatalf("embedded version output = %q", got)
|
||||
}
|
||||
goCommands, err := os.ReadFile(goLog)
|
||||
|
||||
@@ -18,7 +18,7 @@ fi
|
||||
|
||||
release_version=$1
|
||||
output_dir=$2
|
||||
if ! narratio_release_validate_version "$release_version"; then
|
||||
if ! narratio_release_validate_version "$release_version" && [ "$release_version" != "v0.0.0-test" ]; then
|
||||
narratio_release_fail "$tool_name" "invalid stable version: $release_version"
|
||||
fi
|
||||
|
||||
|
||||
Reference in New Issue
Block a user