Seal ignored-workspace routing at Domain startup

This commit is contained in:
ms2k 2026-07-30 00:52:58 +09:00
commit 1ad3b84db2
3 changed files with 720 additions and 285 deletions

View file

@ -44,19 +44,18 @@
},
{
"id": "ignored-to-host-policy",
"trigger": "path becomes non-ignored without becoming tracked",
"trigger": "next Domain initialization classifies an isolated path as host-backed",
"new_lookups": "host-or-absent",
"open_handles": "remain-isolated",
"isolated_state": "retained-hidden",
"isolated_state": "deleted-before-readiness",
"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"
"trigger": "next Domain initialization classifies a host-backed path as ignored",
"new_lookups": "host-clone-or-absent",
"host_state": "retained-unchanged",
"isolated_state": "host-clone-replaces-stale-state",
"copy": "host-to-isolated-only"
},
{
"id": "isolated-to-tracked-guest-publication",
@ -65,22 +64,6 @@
"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": [

View file

@ -67,12 +67,12 @@
"expect": ["nested-rules-selected", "host-sentinel-hidden"]
},
{
"id": "live-ignore-publication",
"tags": ["ignore-precedence", "live-policy-change", "concurrent-mutation"],
"id": "startup-sealed-ignore-mutation",
"tags": ["ignore-precedence", "startup-sealed-policy", "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"]
"actions": ["host-mutate-ignore", "sandbox-open-in-same-generation"],
"expect": ["single-classification-epoch", "host-object-remains-visible", "no-copy"]
},
{
"id": "cargo-target-host-to-isolated-rename",
@ -108,19 +108,19 @@
},
{
"id": "ignored-becomes-non-ignored",
"tags": ["ignored-to-tracked-transition", "open-handle-transition", "no-copy-back"],
"tags": ["startup-policy-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"]
"setup": ["isolated-object", "host-object", "path-ignored"],
"actions": ["stop-domain", "host-negate-ignore", "start-domain"],
"expect": ["host-object-visible", "isolated-object-deleted", "no-copy-back"]
},
{
"id": "non-ignored-becomes-ignored",
"tags": ["ignored-to-tracked-transition", "open-handle-transition", "no-copy-back"],
"tags": ["startup-policy-transition", "host-to-isolated-copy", "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"]
"setup": ["host-object", "stale-isolated-object", "path-initially-non-ignored"],
"actions": ["stop-domain", "host-publish-ignore", "start-domain"],
"expect": ["isolated-clone-visible", "stale-isolated-replaced", "host-object-unchanged"]
},
{
"id": "guest-force-add-isolated-only",
@ -132,35 +132,35 @@
},
{
"id": "host-established-tracked-cutover",
"tags": ["ignored-to-tracked-transition", "open-handle-transition", "no-copy-back"],
"tags": ["ignored-to-tracked-transition", "startup-policy-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"]
"setup": ["isolated-object", "path-ignored"],
"actions": ["host-create-type-compatible-object", "host-publish-tracked-index", "sandbox-open-in-same-generation", "restart-domain"],
"expect": ["same-generation-route-isolated", "next-generation-route-host", "isolated-object-deleted", "no-copy-back"]
},
{
"id": "unmediated-inconsistent-index",
"tags": ["ignored-to-tracked-transition", "no-silent-loss", "concurrent-mutation"],
"tags": ["ignored-to-tracked-transition", "startup-policy-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"]
"actions": ["host-publish-tracked-index", "sandbox-lookup-in-same-generation", "restart-domain"],
"expect": ["same-generation-route-isolated", "next-generation-path-absent", "isolated-object-deleted", "host-path-absent"]
},
{
"id": "mmap-policy-transition",
"tags": ["mmap", "open-handle-transition", "no-copy-back"],
"tags": ["mmap", "startup-sealed-policy", "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"]
"expect": ["same-generation-route-isolated", "isolated-object-updated", "host-object-unchanged", "no-copy"]
},
{
"id": "locks-policy-transition",
"tags": ["locks", "open-handle-transition"],
"tags": ["locks", "startup-sealed-policy"],
"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"]
"expect": ["same-generation-lock-domain-isolated", "host-lock-domain-independent", "no-lock-transfer"]
},
{
"id": "extended-attributes-and-acls",
@ -204,11 +204,11 @@
},
{
"id": "directory-stream-policy-snapshot",
"tags": ["concurrent-mutation", "open-handle-transition", "ignore-precedence"],
"tags": ["concurrent-mutation", "startup-sealed-policy", "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"]
"expect": ["old-stream-single-snapshot", "new-stream-same-generation-snapshot", "no-duplicate-name"]
},
{
"id": "git-ordinary-workflow",

View file

@ -179,14 +179,17 @@ class IgnoredWorkspaceContractTests(unittest.TestCase):
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:
def test_transitions_copy_only_toward_isolated_state_between_generations(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, ["qualified-host-to-isolated-rename"])
self.assertEqual(
copying,
["qualified-host-to-isolated-rename", "host-to-ignored-policy"],
)
handle_transitions = {
transition["id"]: transition["open_handles"]
for transition in transitions
@ -194,11 +197,7 @@ class IgnoredWorkspaceContractTests(unittest.TestCase):
}
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:
@ -251,19 +250,18 @@ class IgnoredWorkspaceContractTests(unittest.TestCase):
},
{
"id": "ignored-to-host-policy",
"trigger": "path becomes non-ignored without becoming tracked",
"trigger": "next Domain initialization classifies an isolated path as host-backed",
"new_lookups": "host-or-absent",
"open_handles": "remain-isolated",
"isolated_state": "retained-hidden",
"isolated_state": "deleted-before-readiness",
"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",
"trigger": "next Domain initialization classifies a host-backed path as ignored",
"new_lookups": "host-clone-or-absent",
"host_state": "retained-unchanged",
"isolated_state": "host-clone-replaces-stale-state",
"copy": "host-to-isolated-only",
},
{
"id": "isolated-to-tracked-guest-publication",
@ -273,22 +271,6 @@ class IgnoredWorkspaceContractTests(unittest.TestCase):
"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",
},
],
)
@ -372,7 +354,9 @@ class IgnoredWorkspaceContractTests(unittest.TestCase):
"concurrent-mutation",
"cross-policy-rename",
"ignored-to-tracked-transition",
"open-handle-transition",
"startup-sealed-policy",
"startup-policy-transition",
"host-to-isolated-copy",
"no-silent-loss",
"no-copy-back",
}
@ -384,7 +368,7 @@ class IgnoredWorkspaceContractTests(unittest.TestCase):
cases["cross-route-file-rename"]["expect"],
["EXDEV", "source-unchanged", "destination-absent"],
)
self.assertIn("isolated-object-recoverable", cases["unmediated-inconsistent-index"]["expect"])
self.assertIn("isolated-object-deleted", 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"])