Integrate ignored workspace contract
This commit is contained in:
commit
ccf23ccac1
5 changed files with 1254 additions and 0 deletions
|
|
@ -655,6 +655,95 @@ environments, generated executables, caches, and other ignored artifacts from
|
|||
later being executed by host tools. Trusted host configuration may disable this
|
||||
behavior when fully bidirectional ignored paths are required.
|
||||
|
||||
#### Ignored Workspace State routing contract
|
||||
|
||||
The production router is a dedicated rootless host FUSE filesystem. It presents
|
||||
one workspace tree and chooses exactly one backing for each pathname operation:
|
||||
the canonical Agent Workspace for tracked and ordinary non-ignored paths, or the
|
||||
Domain's Ignored Workspace State for ignored untracked paths. The `.git` entry is
|
||||
host-backed, subject to the Git Sentry mounts below. Classification uses Git's
|
||||
effective byte-path rules, including repository and nested `.gitignore` files,
|
||||
negation and precedence, the applicable common-directory `info/exclude`, the
|
||||
configured global excludes file, tracked-index state, linked worktrees, nested
|
||||
repositories, and `core.ignoreCase`. Non-UTF-8 path components are supported as
|
||||
opaque bytes; NUL and `/` retain their ordinary pathname meaning and cannot be
|
||||
components.
|
||||
|
||||
Each pathname operation uses one immutable classification snapshot. Publication
|
||||
of a changed index or ignore input serializes against new pathname operations and
|
||||
either installs one complete new snapshot or leaves the prior snapshot active.
|
||||
There is no partially updated matcher. Existing file descriptions, mappings,
|
||||
locks, and directory streams remain pinned to the backing object and snapshot
|
||||
under which they were opened. A subsequent lookup or open uses the current
|
||||
snapshot. Writes, flushes, locks, and mapping writeback through an old handle
|
||||
affect only its original backing object; a transition never rebinds a handle or
|
||||
copies its content to the other backing.
|
||||
|
||||
Directory lookup and enumeration expose only the entry selected by the current
|
||||
snapshot. An ignored host entry is hidden even when no isolated entry exists. A
|
||||
stale isolated entry is retained but hidden while its pathname is host-backed.
|
||||
On a non-ignored-to-ignored transition, subsequent lookups expose the retained
|
||||
isolated entry or absence and hide the host entry. On an
|
||||
ignored-to-non-ignored transition, subsequent lookups expose the host entry or
|
||||
absence and retain the isolated entry hidden. Neither transition imports,
|
||||
exports, merges, deletes, or otherwise copies content between backings.
|
||||
|
||||
A same-backing `rename` or hard link has the backing filesystem's atomicity and
|
||||
error behavior. If the source and destination route to different backings,
|
||||
`rename`, `RENAME_NOREPLACE`, `RENAME_EXCHANGE`, and hard link return `EXDEV`
|
||||
before either backing is changed. `RENAME_WHITEOUT` returns `EOPNOTSUPP` without
|
||||
mutation. Lazaret never reports a copy-and-unlink sequence as a successful
|
||||
rename or a copied file as a hard link. Applications may react to `EXDEV` with
|
||||
their own visible copy and unlink operations.
|
||||
|
||||
An index publication that would make an isolated object tracked is accepted only
|
||||
when a type-compatible host worktree object already exists independently. The
|
||||
router then selects that host object and retains the isolated object hidden; it
|
||||
does not compare, merge, or copy isolated bytes. A guest index-lock rename that
|
||||
would instead create a tracked path with only an isolated object returns `EBUSY`
|
||||
before replacing the index. If an unmediated host index change creates that
|
||||
state, pathname access to the inconsistent path fails with `EIO` until the host
|
||||
index/worktree tuple is made consistent or reverted. The isolated object remains
|
||||
recoverable Domain state. Consequently, ordinary add and commit are
|
||||
supported for host-backed paths, while force-adding an isolated-only path is an
|
||||
explicitly unsupported Git operation rather than implicit host copy-back or
|
||||
silent disappearance.
|
||||
|
||||
The v1 operation contract supports lookup, bytewise directory enumeration,
|
||||
regular-file and directory creation, open, read, write, truncate, allocation,
|
||||
sync, unlink, and same-backing rename and hard link. It supports symbolic links
|
||||
without following a backing symlink during routing, nanosecond timestamps,
|
||||
`user.*` extended attributes, POSIX ACLs where the backing filesystem supports
|
||||
them, sparse-file hole/data queries and hole punching, same-backing copy range,
|
||||
shared and private mappings, BSD `flock`, POSIX record locks, FIFOs, and Unix
|
||||
domain sockets. Cross-backing copy range returns `EXDEV`. Character and block
|
||||
device creation returns `EPERM`. Backing filesystems may return their native
|
||||
documented unsupported error for an optional feature, but the router may not
|
||||
turn a partial operation into success or fall through to the other backing.
|
||||
|
||||
The supported Git contract covers status, diff, ordinary add and reset, commit,
|
||||
checkout and restore, branch and reference updates, merge, rebase, stash,
|
||||
worktree-local index locking, linked worktrees, and nested repositories. These
|
||||
operations retain Git's ordinary conflict and concurrent-writer behavior. Git
|
||||
Sentry still makes host `.git/config` read-only and replaces default
|
||||
`.git/hooks` with sandbox-local transient storage. Force-adding an isolated-only
|
||||
path, cross-policy atomic rename, and device-node creation are refused as defined
|
||||
above and are not compatibility promises.
|
||||
|
||||
The committed `lazaret-ignored-workspace-contract-v1` corpus is the executable
|
||||
acceptance inventory for these semantics. It covers ignore precedence and live
|
||||
changes, non-UTF-8 and case-folded matching, linked worktrees and nested
|
||||
repositories, every operation class above, open-handle transitions, concurrent
|
||||
host and sandbox mutation, router loss, and absence of silent loss or copy-back.
|
||||
The same corpus defines the benchmark protocol. Qualification runs direct-host
|
||||
and routed placements on the recorded reference environment, uses identical
|
||||
fresh fixture materialization for each sample, and reports both the routed
|
||||
absolute median and the paired routed/direct median ratio. Metadata, Git, offline
|
||||
package installation, and offline build workloads must meet both limits. The
|
||||
reference environment, preparation states, sampling order, variance tolerance,
|
||||
normalization, and numeric budgets are part of the corpus and may not be relaxed
|
||||
by a production implementation.
|
||||
|
||||
Ignored-path isolation is defense in depth and does not replace the mandatory Git
|
||||
Sentry protections below.
|
||||
|
||||
|
|
|
|||
143
tests/fixtures/ignored-workspace-v1/benchmark.json
vendored
Normal file
143
tests/fixtures/ignored-workspace-v1/benchmark.json
vendored
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
{
|
||||
"schema": "lazaret-ignored-workspace-benchmark-v1",
|
||||
"reference_environment": {
|
||||
"architecture": "x86_64",
|
||||
"kernel": "Linux 6.9 or later",
|
||||
"cpu": "reference x86_64 processor",
|
||||
"cpu_set": "one hardware thread from each of eight physical cores",
|
||||
"cpu_governor": "performance",
|
||||
"memory_bytes": 17179869184,
|
||||
"swap": "disabled-for-benchmark-cgroup",
|
||||
"storage": "reference local NVMe-class block device",
|
||||
"storage_stack": "NVMe with dm-crypt and no network or copy-on-write layer",
|
||||
"filesystem": "XFS",
|
||||
"filesystem_block_bytes": 4096,
|
||||
"filesystem_mount_options": [
|
||||
"rw",
|
||||
"nosuid",
|
||||
"nodev",
|
||||
"relatime",
|
||||
"inode64",
|
||||
"noquota"
|
||||
],
|
||||
"fuse_userspace": "qualification-result-version",
|
||||
"runsc": "qualification-result-version",
|
||||
"git": "qualification-result-version",
|
||||
"uv": "qualification-result-version",
|
||||
"rust": "qualification-result-version"
|
||||
},
|
||||
"placements": {
|
||||
"direct": "one native reference tree containing the same visible bytes and metadata on the same backing filesystem, without FUSE or runsc",
|
||||
"routed": "the same backing filesystem through the production FUSE router and supported runsc"
|
||||
},
|
||||
"sampling": {
|
||||
"independent_runs": 3,
|
||||
"warmup_samples_per_cell": 2,
|
||||
"measured_samples_per_cell": 15,
|
||||
"order": "paired alternating direct-routed then routed-direct",
|
||||
"clock": "CLOCK_MONOTONIC_RAW elapsed nanoseconds",
|
||||
"statistic": "median",
|
||||
"relative_statistic": "median of paired routed/direct elapsed-time ratios",
|
||||
"median_definition": "sort numeric values ascending and select value at zero-based index floor(count/2); every measured count is odd",
|
||||
"within_run_relative_mad_formula": "median(abs(sample - median(samples))) / median(samples), evaluated separately for direct elapsed times, routed elapsed times, and paired routed/direct ratios in each workload, preparation state, and independent run",
|
||||
"between_run_median_spread_formula": "(max(run_medians) - min(run_medians)) / median(run_medians), evaluated across the three independent-run medians separately for direct elapsed time, routed elapsed time, and paired routed/direct ratio in each workload and preparation state",
|
||||
"outlier_policy": "retain every completed measured sample",
|
||||
"sample_timeout": "four times the workload absolute budget",
|
||||
"within_run_relative_mad_limit": 0.10,
|
||||
"between_run_median_spread_limit": 0.10,
|
||||
"budget_tolerance": 0.0
|
||||
},
|
||||
"preparation_states": {
|
||||
"unprimed": "fresh uniquely named materialization, empty workload output directories, and no fixture prewalk; operating-system cache dropping is not required",
|
||||
"primed": "fresh materialization followed by one untimed complete workload invocation before measured samples"
|
||||
},
|
||||
"normalization": {
|
||||
"environment": "reject a run that differs from any reference environment field",
|
||||
"background_load": "reject a sample when another process consumes more than five percent of one reference core or benchmark memory pressure causes reclaim",
|
||||
"pass_rule": "every independent run and preparation state meets both the absolute median and paired median-ratio budget and both variance limits"
|
||||
},
|
||||
"identity_encoding": {
|
||||
"integer_encoding": "unsigned integers are fixed-width big-endian; the command exit status is signed two's-complement i32 big-endian",
|
||||
"fixture": {
|
||||
"context_hex": "6c617a617265742d69676e6f7265642d776f726b73706163652d666978747572652d763100",
|
||||
"root_entry": "omitted",
|
||||
"path": "raw relative pathname bytes with slash separators, no leading or trailing slash, and no NUL",
|
||||
"ordering": "ascending lexicographic order of complete raw path byte strings",
|
||||
"kind_tags": {
|
||||
"directory": 1,
|
||||
"regular": 2,
|
||||
"symlink": 3
|
||||
},
|
||||
"mode": "u32 of st_mode masked with octal 7777",
|
||||
"size": "u64 payload length for regular files and symlinks; zero for directories",
|
||||
"payload": "regular file bytes, raw symlink target bytes, or empty bytes for a directory",
|
||||
"record": "u32(path length) || path || u8(kind tag) || u32(mode) || u64(size) || SHA-256(payload)",
|
||||
"stream": "context bytes || u32(record count) || records in declared ordering",
|
||||
"identity": "SHA-256(stream)",
|
||||
"unsupported_entry": "reject identity construction for any other file kind"
|
||||
},
|
||||
"result": {
|
||||
"context_hex": "6c617a617265742d69676e6f7265642d776f726b73706163652d726573756c742d763100",
|
||||
"workload": "u32(UTF-8 workload ID length) || UTF-8 workload ID",
|
||||
"command_record": "i32(exit status) || u64(stdout length) || exact stdout bytes || u64(stderr length) || exact stderr bytes",
|
||||
"ordering": "command records remain in command_sequence order",
|
||||
"stream": "context bytes || framed workload || initial fixture identity || u32(command record count) || ordered command records || post-workload fixture identity",
|
||||
"identity": "SHA-256(stream)",
|
||||
"successful_sample": "every command exit status is zero and both fixture identities and every output byte are retained"
|
||||
}
|
||||
},
|
||||
"workloads": [
|
||||
{
|
||||
"id": "metadata-10000",
|
||||
"category": "metadata",
|
||||
"fixture_seed": "b4e3a0d11bcd823e8ea7e9fc8a2e4ec4f4cb628e8df62df28ba369ecb2944dd7",
|
||||
"fixture": "100 directories each containing 100 regular files of 64 bytes; half host-backed and half ignored isolated",
|
||||
"fixture_algorithm": "directories d000 through d099; files f000 through f099; content is the 32-byte SHA-256 of fixture_seed, slash, and byte path repeated twice; even directories are host-backed and odd directories are ignored isolated",
|
||||
"operation": "bytewise walk, lstat, open, read 64 bytes, close, and statx each file",
|
||||
"command_sequence": [["lazaret-workspace-benchmark", "metadata", "--root", "ROOT", "--bytewise-order", "--statx-mask", "basic-stats"]],
|
||||
"result_oracle": "exactly 10000 regular files, 640000 content bytes, and the canonical fixture identity",
|
||||
"preparation_states": ["unprimed", "primed"],
|
||||
"absolute_median_ms": 2000,
|
||||
"relative_median_ratio": 25.0
|
||||
},
|
||||
{
|
||||
"id": "git-2000-status-add-reset",
|
||||
"category": "git",
|
||||
"fixture_seed": "7ff9a3db8752e32d8d782c4c8d09d733be72f9dcba9d49d681b31193749f3ef4",
|
||||
"fixture": "1000 tracked files, 500 ordinary untracked files, 500 ignored isolated files, and ignored host sentinels",
|
||||
"fixture_algorithm": "tracked/t0000 through t0999 are committed; ordinary/u0000 through u0499 are untracked; ignored/i0000 through i0499 match one root ignore rule and have distinct host sentinel and isolated content derived from fixture_seed and byte path",
|
||||
"operation": "porcelain-v2 status, add the ordinary untracked set, quiet mixed reset, and porcelain-v2 status",
|
||||
"command_sequence": [["git", "status", "--porcelain=v2", "--untracked-files=all"], ["git", "add", "--", "ordinary"], ["git", "reset", "--quiet", "--mixed"], ["git", "status", "--porcelain=v2", "--untracked-files=all"]],
|
||||
"result_oracle": "both status byte streams equal the locked direct placement streams, the index returns to its initial identity, and every ignored host sentinel is unchanged",
|
||||
"preparation_states": ["unprimed", "primed"],
|
||||
"absolute_median_ms": 2500,
|
||||
"relative_median_ratio": 8.0
|
||||
},
|
||||
{
|
||||
"id": "uv-offline-32-wheels",
|
||||
"category": "package-manager",
|
||||
"fixture_seed": "df5d5a7d287cc0a140d95c77a5f560db66929a138d5dfc854a6af4f90d609184",
|
||||
"fixture": "one root wheel and 31 deterministic local dependency wheels with no build scripts or network access",
|
||||
"fixture_algorithm": "packages lazaret-bench-pkg-00 through lazaret-bench-pkg-31 at version 1.0.0; package 00 depends exactly on packages 01 through 31; each pure-Python wheel contains one module whose VALUE is the SHA-256 of fixture_seed and normalized package name",
|
||||
"operation": "uv pip install --offline --no-index into a fresh ignored virtual environment",
|
||||
"command_sequence": [["uv", "pip", "install", "--offline", "--no-index", "--find-links", "WHEELHOUSE", "--python", "VENV/bin/python", "lazaret-bench-pkg-00==1.0.0"]],
|
||||
"result_oracle": "all 32 exact distributions are installed and importing every module yields its fixture-derived VALUE",
|
||||
"preparation_states": ["unprimed", "primed"],
|
||||
"absolute_median_ms": 5000,
|
||||
"relative_median_ratio": 6.0
|
||||
},
|
||||
{
|
||||
"id": "cargo-offline-32-crates",
|
||||
"category": "build",
|
||||
"fixture_seed": "42324c58067ac0869fb4ec486f42fec74e0f3147a12aa2aee80af45c3bb8595d",
|
||||
"fixture": "one binary and 31 path-library crates with deterministic generated Rust sources and an exact lockfile",
|
||||
"fixture_algorithm": "workspace packages lazaret-bench-crate-00 through lazaret-bench-crate-31 at version 1.0.0; package 00 is a binary with exact path dependencies on libraries 01 through 31; each library returns the first eight bytes of SHA-256 of fixture_seed and package name as a u64 constant",
|
||||
"operation": "cargo check --offline --locked --workspace with a fresh ignored target directory",
|
||||
"command_sequence": [["cargo", "check", "--offline", "--locked", "--workspace", "--target-dir", "TARGET"]],
|
||||
"result_oracle": "exit zero, no lockfile change, no source-tree change, and all output remains beneath the ignored target directory",
|
||||
"preparation_states": ["unprimed", "primed"],
|
||||
"absolute_median_ms": 8000,
|
||||
"relative_median_ratio": 5.0
|
||||
}
|
||||
]
|
||||
}
|
||||
228
tests/fixtures/ignored-workspace-v1/contract.json
vendored
Normal file
228
tests/fixtures/ignored-workspace-v1/contract.json
vendored
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
{
|
||||
"schema": "lazaret-ignored-workspace-contract-v1",
|
||||
"mechanism": "rootless-host-fuse-router",
|
||||
"route_classes": [
|
||||
"host",
|
||||
"isolated"
|
||||
],
|
||||
"transition_contracts": [
|
||||
{
|
||||
"id": "cross-route-rename",
|
||||
"operations": [
|
||||
"rename",
|
||||
"rename-noreplace",
|
||||
"rename-exchange"
|
||||
],
|
||||
"result": "EXDEV",
|
||||
"mutation": "none"
|
||||
},
|
||||
{
|
||||
"id": "rename-whiteout",
|
||||
"operations": [
|
||||
"rename-whiteout"
|
||||
],
|
||||
"result": "EOPNOTSUPP",
|
||||
"mutation": "none"
|
||||
},
|
||||
{
|
||||
"id": "cross-route-hard-link",
|
||||
"operations": [
|
||||
"link"
|
||||
],
|
||||
"result": "EXDEV",
|
||||
"mutation": "none"
|
||||
},
|
||||
{
|
||||
"id": "ignored-to-host-policy",
|
||||
"trigger": "path becomes non-ignored without becoming tracked",
|
||||
"new_lookups": "host-or-absent",
|
||||
"open_handles": "remain-isolated",
|
||||
"isolated_state": "retained-hidden",
|
||||
"copy": "none"
|
||||
},
|
||||
{
|
||||
"id": "host-to-ignored-policy",
|
||||
"trigger": "untracked path becomes ignored",
|
||||
"new_lookups": "retained-isolated-or-absent",
|
||||
"open_handles": "remain-host",
|
||||
"host_state": "retained-hidden",
|
||||
"copy": "none"
|
||||
},
|
||||
{
|
||||
"id": "isolated-to-tracked-guest-publication",
|
||||
"trigger": "guest index publication tracks an isolated-only path",
|
||||
"result": "EBUSY",
|
||||
"index_mutation": "none",
|
||||
"isolated_state": "retained-visible",
|
||||
"copy": "none"
|
||||
},
|
||||
{
|
||||
"id": "isolated-to-tracked-host-established",
|
||||
"trigger": "published index and type-compatible host object both exist",
|
||||
"new_lookups": "host",
|
||||
"open_handles": "remain-isolated",
|
||||
"isolated_state": "retained-hidden",
|
||||
"copy": "none"
|
||||
},
|
||||
{
|
||||
"id": "isolated-to-tracked-unmediated-inconsistent",
|
||||
"trigger": "unmediated index publication tracks a path without a host object",
|
||||
"result": "EIO",
|
||||
"path_state": "fail-closed-until-consistent",
|
||||
"isolated_state": "retained-hidden-recoverable",
|
||||
"copy": "none"
|
||||
}
|
||||
],
|
||||
"filesystem_operations": [
|
||||
{
|
||||
"operation": "lookup-getattr-readdir",
|
||||
"host": "supported",
|
||||
"isolated": "supported",
|
||||
"cross_route": "not-applicable"
|
||||
},
|
||||
{
|
||||
"operation": "create-mkdir-open-read-write-truncate-fallocate-fsync",
|
||||
"host": "supported",
|
||||
"isolated": "supported",
|
||||
"cross_route": "not-applicable"
|
||||
},
|
||||
{
|
||||
"operation": "unlink-rmdir",
|
||||
"host": "supported",
|
||||
"isolated": "supported",
|
||||
"cross_route": "not-applicable"
|
||||
},
|
||||
{
|
||||
"operation": "rename",
|
||||
"host": "supported",
|
||||
"isolated": "supported",
|
||||
"cross_route": "EXDEV"
|
||||
},
|
||||
{
|
||||
"operation": "rename-noreplace",
|
||||
"host": "supported",
|
||||
"isolated": "supported",
|
||||
"cross_route": "EXDEV"
|
||||
},
|
||||
{
|
||||
"operation": "rename-exchange",
|
||||
"host": "supported",
|
||||
"isolated": "supported",
|
||||
"cross_route": "EXDEV"
|
||||
},
|
||||
{
|
||||
"operation": "rename-whiteout",
|
||||
"host": "EOPNOTSUPP",
|
||||
"isolated": "EOPNOTSUPP",
|
||||
"cross_route": "EOPNOTSUPP"
|
||||
},
|
||||
{
|
||||
"operation": "hard-link",
|
||||
"host": "supported",
|
||||
"isolated": "supported",
|
||||
"cross_route": "EXDEV"
|
||||
},
|
||||
{
|
||||
"operation": "symlink-readlink",
|
||||
"host": "supported",
|
||||
"isolated": "supported",
|
||||
"cross_route": "virtual-path-resolution"
|
||||
},
|
||||
{
|
||||
"operation": "user-xattrs",
|
||||
"host": "backing-feature",
|
||||
"isolated": "backing-feature",
|
||||
"cross_route": "not-applicable"
|
||||
},
|
||||
{
|
||||
"operation": "posix-acls",
|
||||
"host": "backing-feature",
|
||||
"isolated": "backing-feature",
|
||||
"cross_route": "not-applicable"
|
||||
},
|
||||
{
|
||||
"operation": "sparse-seek-hole-data-punch-hole",
|
||||
"host": "backing-feature",
|
||||
"isolated": "backing-feature",
|
||||
"cross_route": "not-applicable"
|
||||
},
|
||||
{
|
||||
"operation": "copy-file-range",
|
||||
"host": "supported",
|
||||
"isolated": "supported",
|
||||
"cross_route": "EXDEV"
|
||||
},
|
||||
{
|
||||
"operation": "utimens-nanoseconds",
|
||||
"host": "supported",
|
||||
"isolated": "supported",
|
||||
"cross_route": "not-applicable"
|
||||
},
|
||||
{
|
||||
"operation": "mmap-shared-private",
|
||||
"host": "supported",
|
||||
"isolated": "supported",
|
||||
"cross_route": "pinned-open-backing"
|
||||
},
|
||||
{
|
||||
"operation": "flock-posix-record-lock",
|
||||
"host": "supported",
|
||||
"isolated": "supported",
|
||||
"cross_route": "pinned-open-backing"
|
||||
},
|
||||
{
|
||||
"operation": "fifo-unix-socket",
|
||||
"host": "supported",
|
||||
"isolated": "supported",
|
||||
"cross_route": "not-applicable"
|
||||
},
|
||||
{
|
||||
"operation": "character-block-device-create",
|
||||
"host": "EPERM",
|
||||
"isolated": "EPERM",
|
||||
"cross_route": "EPERM"
|
||||
}
|
||||
],
|
||||
"git_compatibility": [
|
||||
{
|
||||
"operation": "status-diff",
|
||||
"support": "supported"
|
||||
},
|
||||
{
|
||||
"operation": "ordinary-add-reset-commit",
|
||||
"support": "supported-host-backed-paths"
|
||||
},
|
||||
{
|
||||
"operation": "checkout-restore",
|
||||
"support": "supported"
|
||||
},
|
||||
{
|
||||
"operation": "branch-reference-update",
|
||||
"support": "supported"
|
||||
},
|
||||
{
|
||||
"operation": "merge-rebase-stash",
|
||||
"support": "supported"
|
||||
},
|
||||
{
|
||||
"operation": "linked-worktree",
|
||||
"support": "supported"
|
||||
},
|
||||
{
|
||||
"operation": "nested-repository",
|
||||
"support": "supported"
|
||||
},
|
||||
{
|
||||
"operation": "force-add-isolated-only",
|
||||
"support": "EBUSY-before-index-publication"
|
||||
},
|
||||
{
|
||||
"operation": "host-config-write",
|
||||
"support": "EROFS"
|
||||
},
|
||||
{
|
||||
"operation": "default-hook-host-access",
|
||||
"support": "sandbox-transient-only"
|
||||
}
|
||||
]
|
||||
}
|
||||
246
tests/fixtures/ignored-workspace-v1/corpus.json
vendored
Normal file
246
tests/fixtures/ignored-workspace-v1/corpus.json
vendored
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
{
|
||||
"schema": "lazaret-ignored-workspace-corpus-v1",
|
||||
"path_encoding": "lowercase-hex-bytes",
|
||||
"cases": [
|
||||
{
|
||||
"id": "non-utf8-ignore-match",
|
||||
"tags": ["non-utf8-names", "ignore-precedence"],
|
||||
"path_hex": "fffe2e6f626a",
|
||||
"setup": ["root-ignore-star-dot-obj", "host-sentinel"],
|
||||
"actions": ["sandbox-lookup", "sandbox-create"],
|
||||
"expect": ["host-sentinel-hidden", "isolated-object-created"]
|
||||
},
|
||||
{
|
||||
"id": "root-negation-precedence",
|
||||
"tags": ["ignore-precedence", "negation"],
|
||||
"path_hex": "6f75742f6b6565702e6c6f67",
|
||||
"setup": ["ignore-out", "negate-keep-log"],
|
||||
"actions": ["sandbox-write", "host-write"],
|
||||
"expect": ["host-route", "bidirectional-visibility"]
|
||||
},
|
||||
{
|
||||
"id": "nested-ignore-precedence",
|
||||
"tags": ["ignore-precedence", "nested-ignore"],
|
||||
"path_hex": "612f622f63616368652e62696e",
|
||||
"setup": ["root-negation", "nested-ignore"],
|
||||
"actions": ["sandbox-create"],
|
||||
"expect": ["isolated-object-created", "host-path-absent"]
|
||||
},
|
||||
{
|
||||
"id": "info-exclude",
|
||||
"tags": ["ignore-precedence", "info-exclude"],
|
||||
"path_hex": "6c6f63616c2e736563726574",
|
||||
"setup": ["info-exclude-local-secret", "host-sentinel"],
|
||||
"actions": ["sandbox-lookup"],
|
||||
"expect": ["host-sentinel-hidden"]
|
||||
},
|
||||
{
|
||||
"id": "global-excludes",
|
||||
"tags": ["ignore-precedence", "global-excludes"],
|
||||
"path_hex": "656469746f722e73776170",
|
||||
"setup": ["global-exclude-star-dot-swap", "host-sentinel"],
|
||||
"actions": ["sandbox-lookup"],
|
||||
"expect": ["host-sentinel-hidden"]
|
||||
},
|
||||
{
|
||||
"id": "ignore-case-folding",
|
||||
"tags": ["core-ignore-case", "ignore-precedence"],
|
||||
"path_hex": "4255494c442e4c4f47",
|
||||
"setup": ["core-ignore-case-true", "ignore-build-log", "host-sentinel"],
|
||||
"actions": ["sandbox-lookup"],
|
||||
"expect": ["host-sentinel-hidden"]
|
||||
},
|
||||
{
|
||||
"id": "linked-worktree-index",
|
||||
"tags": ["worktrees", "git-status"],
|
||||
"path_hex": "776f726b747265652d6361636865",
|
||||
"setup": ["linked-worktree", "per-worktree-index", "ignored-host-sentinel"],
|
||||
"actions": ["sandbox-git-status", "sandbox-create"],
|
||||
"expect": ["correct-worktree-index", "host-sentinel-hidden"]
|
||||
},
|
||||
{
|
||||
"id": "nested-repository-boundary",
|
||||
"tags": ["nested-repositories", "ignore-precedence", "git-status"],
|
||||
"path_hex": "76656e646f722f6e65737465642f7461726765742f6465627567",
|
||||
"setup": ["nested-repository", "nested-ignore-target", "host-sentinel"],
|
||||
"actions": ["sandbox-git-status", "sandbox-lookup"],
|
||||
"expect": ["nested-rules-selected", "host-sentinel-hidden"]
|
||||
},
|
||||
{
|
||||
"id": "live-ignore-publication",
|
||||
"tags": ["ignore-precedence", "live-policy-change", "concurrent-mutation"],
|
||||
"path_hex": "67656e6572617465642f6f7574707574",
|
||||
"setup": ["host-object", "path-initially-non-ignored"],
|
||||
"actions": ["host-publish-ignore", "sandbox-open-after-publication"],
|
||||
"expect": ["single-classification-epoch", "host-object-hidden", "no-copy"]
|
||||
},
|
||||
{
|
||||
"id": "cross-route-file-rename",
|
||||
"tags": ["rename", "cross-policy-rename", "no-silent-loss"],
|
||||
"path_hex": "7372632f746d70",
|
||||
"setup": ["host-source", "ignored-destination"],
|
||||
"actions": ["sandbox-rename"],
|
||||
"expect": ["EXDEV", "source-unchanged", "destination-absent"]
|
||||
},
|
||||
{
|
||||
"id": "cross-route-directory-exchange",
|
||||
"tags": ["rename", "cross-policy-rename", "concurrent-mutation"],
|
||||
"path_hex": "63616368652f73776170",
|
||||
"setup": ["host-directory", "isolated-directory"],
|
||||
"actions": ["sandbox-rename-exchange"],
|
||||
"expect": ["EXDEV", "both-trees-unchanged"]
|
||||
},
|
||||
{
|
||||
"id": "cross-route-hard-link",
|
||||
"tags": ["link", "cross-policy-rename", "no-copy-back"],
|
||||
"path_hex": "6275696c642f616c696173",
|
||||
"setup": ["host-source", "ignored-destination"],
|
||||
"actions": ["sandbox-hard-link"],
|
||||
"expect": ["EXDEV", "destination-absent", "no-copy"]
|
||||
},
|
||||
{
|
||||
"id": "ignored-becomes-non-ignored",
|
||||
"tags": ["ignored-to-tracked-transition", "open-handle-transition", "no-copy-back"],
|
||||
"path_hex": "63616368652f64617461",
|
||||
"setup": ["isolated-object", "host-object", "open-isolated-handle"],
|
||||
"actions": ["host-negate-ignore", "sandbox-open-new", "write-old-handle"],
|
||||
"expect": ["new-handle-host", "old-handle-isolated", "no-copy", "both-objects-retained"]
|
||||
},
|
||||
{
|
||||
"id": "non-ignored-becomes-ignored",
|
||||
"tags": ["ignored-to-tracked-transition", "open-handle-transition", "no-copy-back"],
|
||||
"path_hex": "6f75742f64617461",
|
||||
"setup": ["host-object", "retained-isolated-object", "open-host-handle"],
|
||||
"actions": ["host-publish-ignore", "sandbox-open-new", "write-old-handle"],
|
||||
"expect": ["new-handle-isolated", "old-handle-host", "no-copy", "both-objects-retained"]
|
||||
},
|
||||
{
|
||||
"id": "guest-force-add-isolated-only",
|
||||
"tags": ["ignored-to-tracked-transition", "git-add", "no-silent-loss"],
|
||||
"path_hex": "7461726765742f617070",
|
||||
"setup": ["isolated-object", "path-ignored", "host-path-absent"],
|
||||
"actions": ["sandbox-git-add-force"],
|
||||
"expect": ["EBUSY-before-index-publication", "prior-index-retained", "isolated-object-visible", "host-path-absent"]
|
||||
},
|
||||
{
|
||||
"id": "host-established-tracked-cutover",
|
||||
"tags": ["ignored-to-tracked-transition", "open-handle-transition", "no-copy-back"],
|
||||
"path_hex": "6173736574732f67656e657261746564",
|
||||
"setup": ["isolated-object", "open-isolated-handle"],
|
||||
"actions": ["host-create-type-compatible-object", "host-publish-tracked-index", "sandbox-open-new"],
|
||||
"expect": ["new-handle-host", "old-handle-isolated", "isolated-object-retained-hidden", "no-copy"]
|
||||
},
|
||||
{
|
||||
"id": "unmediated-inconsistent-index",
|
||||
"tags": ["ignored-to-tracked-transition", "no-silent-loss", "concurrent-mutation"],
|
||||
"path_hex": "646570732f6c6962",
|
||||
"setup": ["isolated-object", "host-path-absent"],
|
||||
"actions": ["host-publish-tracked-index", "sandbox-lookup"],
|
||||
"expect": ["EIO", "path-fail-closed", "isolated-object-recoverable", "host-path-absent"]
|
||||
},
|
||||
{
|
||||
"id": "mmap-policy-transition",
|
||||
"tags": ["mmap", "open-handle-transition", "no-copy-back"],
|
||||
"path_hex": "63616368652f6d6170706564",
|
||||
"setup": ["isolated-object", "shared-write-mapping"],
|
||||
"actions": ["host-negate-ignore", "mapped-write", "mapping-sync"],
|
||||
"expect": ["isolated-object-updated", "host-object-unchanged", "no-copy"]
|
||||
},
|
||||
{
|
||||
"id": "locks-policy-transition",
|
||||
"tags": ["locks", "open-handle-transition"],
|
||||
"path_hex": "63616368652f6c6f636b6564",
|
||||
"setup": ["isolated-object", "isolated-posix-lock", "host-object"],
|
||||
"actions": ["host-negate-ignore", "sandbox-open-new", "host-lock-attempt"],
|
||||
"expect": ["old-lock-remains-isolated", "new-lock-domain-host", "no-lock-transfer"]
|
||||
},
|
||||
{
|
||||
"id": "extended-attributes-and-acls",
|
||||
"tags": ["xattrs", "acls"],
|
||||
"path_hex": "63616368652f6d65746164617461",
|
||||
"setup": ["host-and-isolated-backing-features"],
|
||||
"actions": ["sandbox-set-get-remove-user-xattr", "sandbox-set-get-posix-acl"],
|
||||
"expect": ["exact-selected-backing-metadata", "native-unsupported-errno-only"]
|
||||
},
|
||||
{
|
||||
"id": "sparse-copy-range",
|
||||
"tags": ["sparse", "copy-range"],
|
||||
"path_hex": "6275696c642f737061727365",
|
||||
"setup": ["isolated-sparse-source", "isolated-destination", "host-destination"],
|
||||
"actions": ["seek-hole-data", "punch-hole", "same-route-copy-range", "cross-route-copy-range"],
|
||||
"expect": ["sparse-extents-preserved", "same-route-bytes-match", "cross-route-EXDEV"]
|
||||
},
|
||||
{
|
||||
"id": "symlink-confinement",
|
||||
"tags": ["symlink", "concurrent-mutation"],
|
||||
"path_hex": "63616368652f6c696e6b",
|
||||
"setup": ["relative-symlink", "outside-looking-target", "rename-racer"],
|
||||
"actions": ["sandbox-readlink", "sandbox-follow", "concurrent-backing-rename"],
|
||||
"expect": ["link-bytes-preserved", "virtual-path-resolution", "no-backing-root-escape"]
|
||||
},
|
||||
{
|
||||
"id": "timestamps",
|
||||
"tags": ["timestamps"],
|
||||
"path_hex": "7372632f74696d6564",
|
||||
"setup": ["host-object"],
|
||||
"actions": ["sandbox-utimens-nanoseconds", "host-stat"],
|
||||
"expect": ["exact-supported-nanoseconds", "bidirectional-visibility"]
|
||||
},
|
||||
{
|
||||
"id": "special-files",
|
||||
"tags": ["special-files"],
|
||||
"path_hex": "63616368652f7370656369616c",
|
||||
"setup": ["isolated-directory"],
|
||||
"actions": ["sandbox-create-fifo", "sandbox-bind-unix-socket", "sandbox-create-device"],
|
||||
"expect": ["fifo-supported", "unix-socket-supported", "device-EPERM"]
|
||||
},
|
||||
{
|
||||
"id": "directory-stream-policy-snapshot",
|
||||
"tags": ["concurrent-mutation", "open-handle-transition", "ignore-precedence"],
|
||||
"path_hex": "6f7574",
|
||||
"setup": ["open-directory-stream", "host-and-isolated-children"],
|
||||
"actions": ["host-publish-ignore-change", "continue-readdir", "open-new-directory-stream"],
|
||||
"expect": ["old-stream-single-snapshot", "new-stream-new-snapshot", "no-duplicate-name"]
|
||||
},
|
||||
{
|
||||
"id": "git-ordinary-workflow",
|
||||
"tags": ["git-status", "git-add", "git-commit", "git-checkout"],
|
||||
"path_hex": "7372632f6c69622e7273",
|
||||
"setup": ["tracked-host-file", "ordinary-untracked-host-file"],
|
||||
"actions": ["sandbox-git-status", "sandbox-git-add", "sandbox-git-commit", "sandbox-git-restore"],
|
||||
"expect": ["host-index-and-objects-updated", "host-worktree-bidirectional", "ignored-sentinel-hidden"]
|
||||
},
|
||||
{
|
||||
"id": "git-history-workflow",
|
||||
"tags": ["git-merge", "git-rebase", "git-stash"],
|
||||
"path_hex": "7372632f686973746f72792e7273",
|
||||
"setup": ["two-host-backed-branches", "host-backed-local-change"],
|
||||
"actions": ["sandbox-git-stash", "sandbox-git-merge", "sandbox-git-rebase"],
|
||||
"expect": ["native-git-outcomes", "references-bidirectional", "no-isolated-copy-back"]
|
||||
},
|
||||
{
|
||||
"id": "git-sentry",
|
||||
"tags": ["git-config", "git-hooks", "git-status"],
|
||||
"path_hex": "2e676974",
|
||||
"setup": ["host-config-sentinel", "host-hook-sentinel"],
|
||||
"actions": ["sandbox-write-git-config", "sandbox-write-default-hook", "sandbox-git-status"],
|
||||
"expect": ["config-EROFS", "host-hook-hidden-unchanged", "sandbox-transient-hook", "git-status-supported"]
|
||||
},
|
||||
{
|
||||
"id": "concurrent-host-sandbox-mutation",
|
||||
"tags": ["concurrent-mutation", "rename", "no-silent-loss"],
|
||||
"path_hex": "7372632f72616365",
|
||||
"setup": ["host-backed-directory", "isolated-sibling"],
|
||||
"actions": ["barrier-concurrent-host-replace", "sandbox-rename-write-unlink", "enumerate-both-backings"],
|
||||
"expect": ["native-host-race-result", "isolated-state-intact", "no-duplicate-name", "no-cross-route-mutation"]
|
||||
},
|
||||
{
|
||||
"id": "router-loss-fails-closed",
|
||||
"tags": ["concurrent-mutation", "no-silent-loss", "no-copy-back"],
|
||||
"path_hex": "63616368652f73656e74696e656c",
|
||||
"setup": ["ignored-host-sentinel", "isolated-object", "mounted-router"],
|
||||
"actions": ["terminate-router", "sandbox-lookup", "force-unmount", "inspect-backings"],
|
||||
"expect": ["access-error-not-host-fallback", "host-sentinel-unchanged", "isolated-object-retained"]
|
||||
}
|
||||
]
|
||||
}
|
||||
548
tests/tooling/test_ignored_workspace_contract.py
Normal file
548
tests/tooling/test_ignored_workspace_contract.py
Normal file
|
|
@ -0,0 +1,548 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
from fractions import Fraction
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import struct
|
||||
import unittest
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
FIXTURE = ROOT / "tests/fixtures/ignored-workspace-v1"
|
||||
EXPECTED_GIT_COMPATIBILITY = {
|
||||
"status-diff": "supported",
|
||||
"ordinary-add-reset-commit": "supported-host-backed-paths",
|
||||
"checkout-restore": "supported",
|
||||
"branch-reference-update": "supported",
|
||||
"merge-rebase-stash": "supported",
|
||||
"linked-worktree": "supported",
|
||||
"nested-repository": "supported",
|
||||
"force-add-isolated-only": "EBUSY-before-index-publication",
|
||||
"host-config-write": "EROFS",
|
||||
"default-hook-host-access": "sandbox-transient-only",
|
||||
}
|
||||
EXPECTED_FORCE_ADD_CASE = {
|
||||
"id": "guest-force-add-isolated-only",
|
||||
"tags": ["ignored-to-tracked-transition", "git-add", "no-silent-loss"],
|
||||
"path_hex": "7461726765742f617070",
|
||||
"setup": ["isolated-object", "path-ignored", "host-path-absent"],
|
||||
"actions": ["sandbox-git-add-force"],
|
||||
"expect": [
|
||||
"EBUSY-before-index-publication",
|
||||
"prior-index-retained",
|
||||
"isolated-object-visible",
|
||||
"host-path-absent",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def load(name: str) -> dict:
|
||||
with (FIXTURE / name).open("rb") as source:
|
||||
return json.load(source)
|
||||
|
||||
|
||||
def odd_median(values: list[int | Fraction]) -> int | Fraction:
|
||||
if not values or len(values) % 2 == 0:
|
||||
raise ValueError("median input must have odd length")
|
||||
ordered = sorted(values)
|
||||
return ordered[len(ordered) // 2]
|
||||
|
||||
|
||||
def relative_mad(values: list[int | Fraction]) -> Fraction:
|
||||
center = odd_median(values)
|
||||
if center == 0:
|
||||
raise ValueError("relative MAD center must be positive")
|
||||
return Fraction(odd_median([abs(value - center) for value in values]), center)
|
||||
|
||||
|
||||
def median_spread(run_medians: list[int]) -> Fraction:
|
||||
center = odd_median(run_medians)
|
||||
if center == 0:
|
||||
raise ValueError("median spread center must be positive")
|
||||
return Fraction(max(run_medians) - min(run_medians), center)
|
||||
|
||||
|
||||
def fixture_identity(records: list[tuple[bytes, str, int, bytes]]) -> bytes:
|
||||
encoding = load("benchmark.json")["identity_encoding"]["fixture"]
|
||||
kinds = encoding["kind_tags"]
|
||||
ordered = sorted(records, key=lambda record: record[0])
|
||||
if len({record[0] for record in ordered}) != len(ordered):
|
||||
raise ValueError("duplicate fixture path")
|
||||
stream = bytearray.fromhex(encoding["context_hex"])
|
||||
stream.extend(struct.pack(">I", len(ordered)))
|
||||
for path, kind, mode, payload in ordered:
|
||||
components = path.split(b"/")
|
||||
if (
|
||||
not path
|
||||
or path.startswith(b"/")
|
||||
or path.endswith(b"/")
|
||||
or b"\x00" in path
|
||||
or any(component in (b"", b".", b"..") for component in components)
|
||||
):
|
||||
raise ValueError("invalid fixture path")
|
||||
if kind not in kinds:
|
||||
raise ValueError("unsupported fixture kind")
|
||||
if kind == "directory" and payload:
|
||||
raise ValueError("directory fixture payload must be empty")
|
||||
size = 0 if kind == "directory" else len(payload)
|
||||
stream.extend(struct.pack(">I", len(path)))
|
||||
stream.extend(path)
|
||||
stream.extend(struct.pack(">B", kinds[kind]))
|
||||
stream.extend(struct.pack(">I", mode & 0o7777))
|
||||
stream.extend(struct.pack(">Q", size))
|
||||
stream.extend(hashlib.sha256(payload).digest())
|
||||
return hashlib.sha256(stream).digest()
|
||||
|
||||
|
||||
def result_identity(
|
||||
workload: str,
|
||||
initial_fixture: bytes,
|
||||
commands: list[tuple[int, bytes, bytes]],
|
||||
post_fixture: bytes,
|
||||
) -> bytes:
|
||||
encoding = load("benchmark.json")["identity_encoding"]["result"]
|
||||
workload_bytes = workload.encode("utf-8")
|
||||
if not workload_bytes or len(initial_fixture) != 32 or len(post_fixture) != 32:
|
||||
raise ValueError("invalid result identity input")
|
||||
stream = bytearray.fromhex(encoding["context_hex"])
|
||||
stream.extend(struct.pack(">I", len(workload_bytes)))
|
||||
stream.extend(workload_bytes)
|
||||
stream.extend(initial_fixture)
|
||||
stream.extend(struct.pack(">I", len(commands)))
|
||||
for exit_status, stdout, stderr in commands:
|
||||
stream.extend(struct.pack(">iQ", exit_status, len(stdout)))
|
||||
stream.extend(stdout)
|
||||
stream.extend(struct.pack(">Q", len(stderr)))
|
||||
stream.extend(stderr)
|
||||
stream.extend(post_fixture)
|
||||
return hashlib.sha256(stream).digest()
|
||||
|
||||
|
||||
def validate_semantic_consistency(contract: dict, corpus: dict) -> None:
|
||||
compatibility = {
|
||||
operation["operation"]: operation["support"]
|
||||
for operation in contract["git_compatibility"]
|
||||
}
|
||||
if compatibility != EXPECTED_GIT_COMPATIBILITY:
|
||||
raise ValueError("Git compatibility differs from the selected contract")
|
||||
transitions = {
|
||||
transition["id"]: transition
|
||||
for transition in contract["transition_contracts"]
|
||||
}
|
||||
guest_publication = transitions["isolated-to-tracked-guest-publication"]
|
||||
if guest_publication != {
|
||||
"id": "isolated-to-tracked-guest-publication",
|
||||
"trigger": "guest index publication tracks an isolated-only path",
|
||||
"result": "EBUSY",
|
||||
"index_mutation": "none",
|
||||
"isolated_state": "retained-visible",
|
||||
"copy": "none",
|
||||
}:
|
||||
raise ValueError("guest force-add transition differs from the selected contract")
|
||||
cases = {case["id"]: case for case in corpus["cases"]}
|
||||
if cases["guest-force-add-isolated-only"] != EXPECTED_FORCE_ADD_CASE:
|
||||
raise ValueError("guest force-add corpus differs from the selected contract")
|
||||
|
||||
|
||||
class IgnoredWorkspaceContractTests(unittest.TestCase):
|
||||
def test_contract_dispatch_and_selected_mechanism_are_exact(self) -> None:
|
||||
contract = load("contract.json")
|
||||
self.assertEqual(contract["schema"], "lazaret-ignored-workspace-contract-v1")
|
||||
self.assertEqual(contract["mechanism"], "rootless-host-fuse-router")
|
||||
self.assertEqual(contract["route_classes"], ["host", "isolated"])
|
||||
|
||||
def test_cross_route_namespace_operations_refuse_before_mutation(self) -> None:
|
||||
transitions = {
|
||||
transition["id"]: transition
|
||||
for transition in load("contract.json")["transition_contracts"]
|
||||
}
|
||||
for transition_id in (
|
||||
"cross-route-rename",
|
||||
"rename-whiteout",
|
||||
"cross-route-hard-link",
|
||||
):
|
||||
with self.subTest(transition_id=transition_id):
|
||||
self.assertEqual(transitions[transition_id]["mutation"], "none")
|
||||
self.assertEqual(transitions["cross-route-rename"]["result"], "EXDEV")
|
||||
self.assertEqual(transitions["cross-route-hard-link"]["result"], "EXDEV")
|
||||
self.assertEqual(transitions["rename-whiteout"]["result"], "EOPNOTSUPP")
|
||||
|
||||
def test_transitions_never_copy_or_rebind_open_handles(self) -> None:
|
||||
transitions = load("contract.json")["transition_contracts"]
|
||||
copying = [
|
||||
transition["id"]
|
||||
for transition in transitions
|
||||
if "copy" in transition and transition["copy"] != "none"
|
||||
]
|
||||
self.assertEqual(copying, [])
|
||||
handle_transitions = {
|
||||
transition["id"]: transition["open_handles"]
|
||||
for transition in transitions
|
||||
if "open_handles" in transition
|
||||
}
|
||||
self.assertEqual(
|
||||
handle_transitions,
|
||||
{
|
||||
"ignored-to-host-policy": "remain-isolated",
|
||||
"host-to-ignored-policy": "remain-host",
|
||||
"isolated-to-tracked-host-established": "remain-isolated",
|
||||
},
|
||||
)
|
||||
|
||||
def test_isolated_only_force_add_is_explicitly_refused(self) -> None:
|
||||
contract = load("contract.json")
|
||||
transitions = {
|
||||
transition["id"]: transition
|
||||
for transition in contract["transition_contracts"]
|
||||
}
|
||||
transition = transitions["isolated-to-tracked-guest-publication"]
|
||||
self.assertEqual(transition["result"], "EBUSY")
|
||||
self.assertEqual(transition["index_mutation"], "none")
|
||||
compatibility = {
|
||||
operation["operation"]: operation["support"]
|
||||
for operation in contract["git_compatibility"]
|
||||
}
|
||||
self.assertEqual(
|
||||
compatibility["force-add-isolated-only"],
|
||||
"EBUSY-before-index-publication",
|
||||
)
|
||||
|
||||
def test_transition_matrix_is_exactly_the_selected_contract(self) -> None:
|
||||
transitions = load("contract.json")["transition_contracts"]
|
||||
self.assertEqual(
|
||||
transitions,
|
||||
[
|
||||
{
|
||||
"id": "cross-route-rename",
|
||||
"operations": ["rename", "rename-noreplace", "rename-exchange"],
|
||||
"result": "EXDEV",
|
||||
"mutation": "none",
|
||||
},
|
||||
{
|
||||
"id": "rename-whiteout",
|
||||
"operations": ["rename-whiteout"],
|
||||
"result": "EOPNOTSUPP",
|
||||
"mutation": "none",
|
||||
},
|
||||
{
|
||||
"id": "cross-route-hard-link",
|
||||
"operations": ["link"],
|
||||
"result": "EXDEV",
|
||||
"mutation": "none",
|
||||
},
|
||||
{
|
||||
"id": "ignored-to-host-policy",
|
||||
"trigger": "path becomes non-ignored without becoming tracked",
|
||||
"new_lookups": "host-or-absent",
|
||||
"open_handles": "remain-isolated",
|
||||
"isolated_state": "retained-hidden",
|
||||
"copy": "none",
|
||||
},
|
||||
{
|
||||
"id": "host-to-ignored-policy",
|
||||
"trigger": "untracked path becomes ignored",
|
||||
"new_lookups": "retained-isolated-or-absent",
|
||||
"open_handles": "remain-host",
|
||||
"host_state": "retained-hidden",
|
||||
"copy": "none",
|
||||
},
|
||||
{
|
||||
"id": "isolated-to-tracked-guest-publication",
|
||||
"trigger": "guest index publication tracks an isolated-only path",
|
||||
"result": "EBUSY",
|
||||
"index_mutation": "none",
|
||||
"isolated_state": "retained-visible",
|
||||
"copy": "none",
|
||||
},
|
||||
{
|
||||
"id": "isolated-to-tracked-host-established",
|
||||
"trigger": "published index and type-compatible host object both exist",
|
||||
"new_lookups": "host",
|
||||
"open_handles": "remain-isolated",
|
||||
"isolated_state": "retained-hidden",
|
||||
"copy": "none",
|
||||
},
|
||||
{
|
||||
"id": "isolated-to-tracked-unmediated-inconsistent",
|
||||
"trigger": "unmediated index publication tracks a path without a host object",
|
||||
"result": "EIO",
|
||||
"path_state": "fail-closed-until-consistent",
|
||||
"isolated_state": "retained-hidden-recoverable",
|
||||
"copy": "none",
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
def test_git_compatibility_is_exactly_the_selected_contract(self) -> None:
|
||||
compatibility = {
|
||||
operation["operation"]: operation["support"]
|
||||
for operation in load("contract.json")["git_compatibility"]
|
||||
}
|
||||
self.assertEqual(
|
||||
compatibility,
|
||||
EXPECTED_GIT_COMPATIBILITY,
|
||||
)
|
||||
|
||||
def test_operation_matrix_covers_required_surface(self) -> None:
|
||||
operations = {
|
||||
operation["operation"]: operation
|
||||
for operation in load("contract.json")["filesystem_operations"]
|
||||
}
|
||||
required = {
|
||||
"lookup-getattr-readdir",
|
||||
"create-mkdir-open-read-write-truncate-fallocate-fsync",
|
||||
"rename",
|
||||
"rename-noreplace",
|
||||
"rename-exchange",
|
||||
"rename-whiteout",
|
||||
"hard-link",
|
||||
"symlink-readlink",
|
||||
"user-xattrs",
|
||||
"posix-acls",
|
||||
"sparse-seek-hole-data-punch-hole",
|
||||
"copy-file-range",
|
||||
"utimens-nanoseconds",
|
||||
"mmap-shared-private",
|
||||
"flock-posix-record-lock",
|
||||
"fifo-unix-socket",
|
||||
"character-block-device-create",
|
||||
}
|
||||
self.assertTrue(required.issubset(operations))
|
||||
self.assertEqual(operations["rename"]["cross_route"], "EXDEV")
|
||||
self.assertEqual(operations["hard-link"]["cross_route"], "EXDEV")
|
||||
self.assertEqual(operations["copy-file-range"]["cross_route"], "EXDEV")
|
||||
self.assertEqual(operations["character-block-device-create"]["host"], "EPERM")
|
||||
|
||||
def test_corpus_ids_and_byte_paths_are_canonical(self) -> None:
|
||||
cases = load("corpus.json")["cases"]
|
||||
ids = [case["id"] for case in cases]
|
||||
self.assertEqual(len(ids), len(set(ids)))
|
||||
for case in cases:
|
||||
with self.subTest(case=case["id"]):
|
||||
path_hex = case["path_hex"]
|
||||
self.assertEqual(path_hex, path_hex.lower())
|
||||
self.assertEqual(bytes.fromhex(path_hex).hex(), path_hex)
|
||||
self.assertNotIn(b"\x00", bytes.fromhex(path_hex))
|
||||
non_utf8 = next(case for case in cases if case["id"] == "non-utf8-ignore-match")
|
||||
with self.assertRaises(UnicodeDecodeError):
|
||||
bytes.fromhex(non_utf8["path_hex"]).decode("utf-8")
|
||||
|
||||
def test_corpus_covers_every_required_behavior(self) -> None:
|
||||
cases = load("corpus.json")["cases"]
|
||||
tags = {tag for case in cases for tag in case["tags"]}
|
||||
required = {
|
||||
"non-utf8-names",
|
||||
"ignore-precedence",
|
||||
"core-ignore-case",
|
||||
"worktrees",
|
||||
"nested-repositories",
|
||||
"mmap",
|
||||
"locks",
|
||||
"xattrs",
|
||||
"acls",
|
||||
"sparse",
|
||||
"copy-range",
|
||||
"rename",
|
||||
"link",
|
||||
"symlink",
|
||||
"timestamps",
|
||||
"special-files",
|
||||
"concurrent-mutation",
|
||||
"cross-policy-rename",
|
||||
"ignored-to-tracked-transition",
|
||||
"open-handle-transition",
|
||||
"no-silent-loss",
|
||||
"no-copy-back",
|
||||
}
|
||||
self.assertTrue(required.issubset(tags), sorted(required - tags))
|
||||
|
||||
def test_corpus_asserts_backing_state_for_refusal_and_loss_cases(self) -> None:
|
||||
cases = {case["id"]: case for case in load("corpus.json")["cases"]}
|
||||
self.assertEqual(
|
||||
cases["cross-route-file-rename"]["expect"],
|
||||
["EXDEV", "source-unchanged", "destination-absent"],
|
||||
)
|
||||
self.assertIn("isolated-object-recoverable", cases["unmediated-inconsistent-index"]["expect"])
|
||||
self.assertIn("access-error-not-host-fallback", cases["router-loss-fails-closed"]["expect"])
|
||||
self.assertIn("host-sentinel-unchanged", cases["router-loss-fails-closed"]["expect"])
|
||||
|
||||
def test_force_add_corpus_is_exactly_fail_closed_without_copy_back(self) -> None:
|
||||
cases = {case["id"]: case for case in load("corpus.json")["cases"]}
|
||||
self.assertEqual(
|
||||
cases["guest-force-add-isolated-only"],
|
||||
EXPECTED_FORCE_ADD_CASE,
|
||||
)
|
||||
|
||||
def test_reproduced_semantic_mutations_are_refused(self) -> None:
|
||||
contract = load("contract.json")
|
||||
corpus = load("corpus.json")
|
||||
validate_semantic_consistency(contract, corpus)
|
||||
|
||||
unsupported_merge = copy.deepcopy(contract)
|
||||
merge = next(
|
||||
operation
|
||||
for operation in unsupported_merge["git_compatibility"]
|
||||
if operation["operation"] == "merge-rebase-stash"
|
||||
)
|
||||
merge["support"] = "unsupported"
|
||||
with self.assertRaisesRegex(ValueError, "Git compatibility"):
|
||||
validate_semantic_consistency(unsupported_merge, corpus)
|
||||
|
||||
copied_force_add = copy.deepcopy(corpus)
|
||||
force_add = next(
|
||||
case
|
||||
for case in copied_force_add["cases"]
|
||||
if case["id"] == "guest-force-add-isolated-only"
|
||||
)
|
||||
force_add["actions"] = [
|
||||
"sandbox-git-add-force",
|
||||
"copy-isolated-bytes-to-host",
|
||||
"publish-index",
|
||||
]
|
||||
force_add["expect"] = ["success", "host-copy", "index-published"]
|
||||
with self.assertRaisesRegex(ValueError, "force-add corpus"):
|
||||
validate_semantic_consistency(contract, copied_force_add)
|
||||
|
||||
def test_benchmark_reference_conditions_are_complete(self) -> None:
|
||||
reference = load("benchmark.json")["reference_environment"]
|
||||
required = {
|
||||
"architecture",
|
||||
"kernel",
|
||||
"cpu",
|
||||
"cpu_set",
|
||||
"cpu_governor",
|
||||
"memory_bytes",
|
||||
"swap",
|
||||
"storage",
|
||||
"storage_stack",
|
||||
"filesystem",
|
||||
"filesystem_block_bytes",
|
||||
"filesystem_mount_options",
|
||||
"fuse_userspace",
|
||||
"runsc",
|
||||
"git",
|
||||
"uv",
|
||||
"rust",
|
||||
}
|
||||
self.assertEqual(set(reference), required)
|
||||
self.assertEqual(reference["architecture"], "x86_64")
|
||||
self.assertEqual(reference["filesystem"], "XFS")
|
||||
self.assertGreaterEqual(reference["memory_bytes"], 16 * 1024**3)
|
||||
|
||||
def test_sampling_protocol_fixes_statistics_and_variance(self) -> None:
|
||||
sampling = load("benchmark.json")["sampling"]
|
||||
self.assertEqual(sampling["independent_runs"], 3)
|
||||
self.assertEqual(sampling["warmup_samples_per_cell"], 2)
|
||||
self.assertEqual(sampling["measured_samples_per_cell"], 15)
|
||||
self.assertEqual(sampling["statistic"], "median")
|
||||
self.assertEqual(
|
||||
sampling["relative_statistic"],
|
||||
"median of paired routed/direct elapsed-time ratios",
|
||||
)
|
||||
self.assertEqual(
|
||||
sampling["within_run_relative_mad_formula"],
|
||||
"median(abs(sample - median(samples))) / median(samples), evaluated separately for direct elapsed times, routed elapsed times, and paired routed/direct ratios in each workload, preparation state, and independent run",
|
||||
)
|
||||
self.assertEqual(
|
||||
sampling["between_run_median_spread_formula"],
|
||||
"(max(run_medians) - min(run_medians)) / median(run_medians), evaluated across the three independent-run medians separately for direct elapsed time, routed elapsed time, and paired routed/direct ratio in each workload and preparation state",
|
||||
)
|
||||
self.assertEqual(sampling["outlier_policy"], "retain every completed measured sample")
|
||||
self.assertLessEqual(sampling["within_run_relative_mad_limit"], 0.10)
|
||||
self.assertLessEqual(sampling["between_run_median_spread_limit"], 0.10)
|
||||
self.assertEqual(sampling["budget_tolerance"], 0.0)
|
||||
|
||||
def test_variance_formulas_distinguish_the_reproduced_values(self) -> None:
|
||||
values = [100, 110, 111]
|
||||
self.assertEqual(relative_mad(values), Fraction(1, 110))
|
||||
self.assertEqual(median_spread(values), Fraction(1, 10))
|
||||
self.assertNotEqual(relative_mad(values), median_spread(values))
|
||||
|
||||
def test_identity_encoding_is_exactly_framed_and_ordered(self) -> None:
|
||||
encoding = load("benchmark.json")["identity_encoding"]
|
||||
self.assertEqual(
|
||||
encoding,
|
||||
{
|
||||
"integer_encoding": "unsigned integers are fixed-width big-endian; the command exit status is signed two's-complement i32 big-endian",
|
||||
"fixture": {
|
||||
"context_hex": "6c617a617265742d69676e6f7265642d776f726b73706163652d666978747572652d763100",
|
||||
"root_entry": "omitted",
|
||||
"path": "raw relative pathname bytes with slash separators, no leading or trailing slash, and no NUL",
|
||||
"ordering": "ascending lexicographic order of complete raw path byte strings",
|
||||
"kind_tags": {"directory": 1, "regular": 2, "symlink": 3},
|
||||
"mode": "u32 of st_mode masked with octal 7777",
|
||||
"size": "u64 payload length for regular files and symlinks; zero for directories",
|
||||
"payload": "regular file bytes, raw symlink target bytes, or empty bytes for a directory",
|
||||
"record": "u32(path length) || path || u8(kind tag) || u32(mode) || u64(size) || SHA-256(payload)",
|
||||
"stream": "context bytes || u32(record count) || records in declared ordering",
|
||||
"identity": "SHA-256(stream)",
|
||||
"unsupported_entry": "reject identity construction for any other file kind",
|
||||
},
|
||||
"result": {
|
||||
"context_hex": "6c617a617265742d69676e6f7265642d776f726b73706163652d726573756c742d763100",
|
||||
"workload": "u32(UTF-8 workload ID length) || UTF-8 workload ID",
|
||||
"command_record": "i32(exit status) || u64(stdout length) || exact stdout bytes || u64(stderr length) || exact stderr bytes",
|
||||
"ordering": "command records remain in command_sequence order",
|
||||
"stream": "context bytes || framed workload || initial fixture identity || u32(command record count) || ordered command records || post-workload fixture identity",
|
||||
"identity": "SHA-256(stream)",
|
||||
"successful_sample": "every command exit status is zero and both fixture identities and every output byte are retained",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
def test_fixture_identity_has_one_exact_canonical_encoding(self) -> None:
|
||||
records = [
|
||||
(b"link", "symlink", 0o777, b"a/\xff"),
|
||||
(b"a/\xff", "regular", 0o640, b"x"),
|
||||
(b"a", "directory", 0o755, b""),
|
||||
]
|
||||
self.assertEqual(
|
||||
fixture_identity(records).hex(),
|
||||
"72f539c740d0048772a2380969c2b22fa970f725958d17b7a534069e92d1994d",
|
||||
)
|
||||
self.assertEqual(fixture_identity(records), fixture_identity(list(reversed(records))))
|
||||
with self.assertRaisesRegex(ValueError, "unsupported fixture kind"):
|
||||
fixture_identity([(b"socket", "socket", 0o600, b"")])
|
||||
|
||||
def test_result_identity_frames_commands_outputs_and_fixture_states(self) -> None:
|
||||
initial = bytes.fromhex("11" * 32)
|
||||
post = bytes.fromhex("22" * 32)
|
||||
commands = [(0, b"a", b""), (1, b"", b"b")]
|
||||
self.assertEqual(
|
||||
result_identity("git", initial, commands, post).hex(),
|
||||
"0fe91ab5411628dc22cb2fb3161486535794c8044d74453c7ab7eafd1c93b4e7",
|
||||
)
|
||||
self.assertNotEqual(
|
||||
result_identity("git", initial, list(reversed(commands)), post),
|
||||
result_identity("git", initial, commands, post),
|
||||
)
|
||||
|
||||
def test_each_workload_has_absolute_and_relative_budgets(self) -> None:
|
||||
benchmark = load("benchmark.json")
|
||||
workloads = benchmark["workloads"]
|
||||
self.assertEqual(
|
||||
{workload["category"] for workload in workloads},
|
||||
{"metadata", "git", "package-manager", "build"},
|
||||
)
|
||||
for workload in workloads:
|
||||
with self.subTest(workload=workload["id"]):
|
||||
self.assertGreater(workload["absolute_median_ms"], 0)
|
||||
self.assertGreater(workload["relative_median_ratio"], 1.0)
|
||||
self.assertEqual(workload["preparation_states"], ["unprimed", "primed"])
|
||||
self.assertEqual(len(workload["fixture_seed"]), 64)
|
||||
self.assertTrue(workload["fixture_algorithm"])
|
||||
self.assertTrue(workload["command_sequence"])
|
||||
self.assertTrue(all(command for command in workload["command_sequence"]))
|
||||
self.assertTrue(workload["result_oracle"])
|
||||
|
||||
def test_specification_names_the_committed_contract(self) -> None:
|
||||
specification = (ROOT / "docs/specification.md").read_text()
|
||||
self.assertIn("#### Ignored Workspace State routing contract", specification)
|
||||
self.assertIn("`lazaret-ignored-workspace-contract-v1`", specification)
|
||||
self.assertIn("rootless host FUSE filesystem", specification)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Loading…
Reference in a new issue