plan-manager-client-dev 0.2.186__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. plan_manager_client/__init__.py +10 -0
  2. plan_manager_client/_version.py +58 -0
  3. plan_manager_client/authoring_diagnostics.py +137 -0
  4. plan_manager_client/client.py +75 -0
  5. plan_manager_client/config.py +320 -0
  6. plan_manager_client/delivery/__init__.py +7 -0
  7. plan_manager_client/delivery/archive.py +140 -0
  8. plan_manager_client/delivery/atomicity.py +93 -0
  9. plan_manager_client/delivery/commit.py +113 -0
  10. plan_manager_client/delivery/destination.py +140 -0
  11. plan_manager_client/delivery/provenance.py +88 -0
  12. plan_manager_client/delivery/upload.py +118 -0
  13. plan_manager_client/dispatch.py +380 -0
  14. plan_manager_client/export_fetch_chunks.py +166 -0
  15. plan_manager_client/export_fetch_request.py +88 -0
  16. plan_manager_client/export_fetch_unpack.py +149 -0
  17. plan_manager_client/export_fetch_verify.py +101 -0
  18. plan_manager_client/facade_agent_config_mixin.py +224 -0
  19. plan_manager_client/facade_bug_project_mixin.py +202 -0
  20. plan_manager_client/facade_context_gate_mixin.py +303 -0
  21. plan_manager_client/facade_entity_common_mixin.py +38 -0
  22. plan_manager_client/facade_hrs_file_mixin.py +206 -0
  23. plan_manager_client/facade_plan_hrs_mixin.py +200 -0
  24. plan_manager_client/facade_runtime_mixin/__init__.py +11 -0
  25. plan_manager_client/facade_runtime_mixin/authoring_part.py +442 -0
  26. plan_manager_client/facade_runtime_mixin/facade.py +380 -0
  27. plan_manager_client/facade_runtime_mixin/overlay_part.py +192 -0
  28. plan_manager_client/facade_step_graph_mixin.py +158 -0
  29. plan_manager_client/id_rule.py +115 -0
  30. plan_manager_client/info_walker.py +133 -0
  31. plan_manager_client/scenario_runtime.py +65 -0
  32. plan_manager_client/server_api.py +427 -0
  33. plan_manager_client/server_api_mutating.py +105 -0
  34. plan_manager_client_dev-0.2.186.dist-info/METADATA +10 -0
  35. plan_manager_client_dev-0.2.186.dist-info/RECORD +37 -0
  36. plan_manager_client_dev-0.2.186.dist-info/WHEEL +5 -0
  37. plan_manager_client_dev-0.2.186.dist-info/top_level.txt +1 -0
@@ -0,0 +1,88 @@
1
+ """Export production request and archive reference resolution (Path B).
2
+
3
+ Nothing on the plan_manager command surface enumerates an export: plan_export reports a
4
+ COUNT of files written, never their names. The whole export tree is therefore delivered as
5
+ ONE archive under one known name, and this module obtains the reference to it: it drives the
6
+ queue-bound export production to completion, then asks the synchronous archive command to
7
+ pack the tree it just wrote, and returns the archive's name, size and declared digest. No
8
+ archive byte is read here, and no file list is requested, derived, or guessed.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from dataclasses import dataclass
14
+ from typing import Any, Protocol
15
+
16
+
17
+ class SupportsExportProduction(Protocol):
18
+ """The two calls this module needs from a client facade."""
19
+
20
+ async def plan_export(
21
+ self, plan: str, revision: str | None = None, include_step_uuids: bool = False
22
+ ) -> dict[str, Any]:
23
+ ...
24
+
25
+ async def export_archive(self, plan: str) -> dict[str, Any]:
26
+ ...
27
+
28
+
29
+ @dataclass(frozen=True)
30
+ class ExportArchiveRef:
31
+ """The reference to one archive holding a whole export tree.
32
+
33
+ Attributes:
34
+ name: The archive's plan-relative name, to be passed to the byte source as-is.
35
+ size_bytes: The archive's size in bytes as the server reported it.
36
+ sha256: The hex sha256 digest of the WHOLE archive, declared by the server.
37
+ file_count: The number of export-tree files packed into the archive.
38
+ """
39
+
40
+ name: str
41
+ size_bytes: int
42
+ sha256: str
43
+ file_count: int
44
+
45
+
46
+ async def request_export_archive(
47
+ client: SupportsExportProduction,
48
+ plan: str,
49
+ revision: str | None = None,
50
+ include_step_uuids: bool = False,
51
+ ) -> ExportArchiveRef:
52
+ """Produce a plan's export tree and obtain the reference to its archive.
53
+
54
+ The export production call is queue-bound and is driven to completion by the client
55
+ facade's queued-command auto-polling before the archive is requested. The archive call
56
+ is synchronous and takes no revision of its own: it packs whatever tree the export
57
+ production call just wrote.
58
+
59
+ Args:
60
+ client: An object exposing the plan_export and export_archive coroutines.
61
+ plan: The plan identifier whose export tree is produced and archived.
62
+ revision: Optional revision passed to the export production call only; when None
63
+ that call uses its own default revision selection.
64
+ include_step_uuids: When True the export descriptors carry step_uuid (bug 62cff4e4
65
+ part b); forwarded to plan_export only when True.
66
+
67
+ Returns:
68
+ The ExportArchiveRef naming exactly one archive file, with its size, declared
69
+ whole-file digest, and packed-file count.
70
+ """
71
+ export_params: dict[str, Any] = {"plan": plan, "revision": revision}
72
+ if include_step_uuids:
73
+ export_params["include_step_uuids"] = True
74
+ await client.plan_export(**export_params)
75
+ response = await client.export_archive(plan=plan)
76
+ return ExportArchiveRef(
77
+ name=str(response["archive"]),
78
+ size_bytes=int(response["size_bytes"]),
79
+ sha256=str(response["sha256"]),
80
+ file_count=int(response["file_count"]),
81
+ )
82
+
83
+
84
+ __all__ = [
85
+ "ExportArchiveRef",
86
+ "SupportsExportProduction",
87
+ "request_export_archive",
88
+ ]
@@ -0,0 +1,149 @@
1
+ """Local unpack of the verified export archive, and the retrieval report (Path B).
2
+
3
+ The whole export tree arrives as one gzip-compressed tar whose entries carry paths relative
4
+ to the plan's export directory. This module expands it onto the caller's filesystem so every
5
+ file reappears byte-for-byte under its original name and relative position, and reports what
6
+ landed. The archive's digest is verified before this module runs; the duty here is boundary
7
+ safety: every entry is checked BEFORE anything is written, so an archive that would escape
8
+ the destination is refused whole rather than leaving a partial tree behind.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import io
14
+ import tarfile
15
+ from dataclasses import dataclass
16
+ from pathlib import Path
17
+ from typing import Sequence
18
+
19
+ from plan_manager_client.export_fetch_verify import VerifiedFile
20
+
21
+
22
+ class ExportUnpackError(RuntimeError):
23
+ """Raised when an archive cannot be unpacked safely.
24
+
25
+ Covers an entry whose path would resolve outside the destination, an entry that is
26
+ neither a regular file nor a directory, and an archive whose bytes are not a readable
27
+ gzip tar. It names only the offending relative entry path: it never carries
28
+ connection, credential, or server-side path information.
29
+ """
30
+
31
+ def __init__(self, entry: str, detail: str) -> None:
32
+ super().__init__(f"cannot unpack archive entry {entry!r}: {detail}")
33
+ self.entry = entry
34
+ self.detail = detail
35
+
36
+
37
+ @dataclass(frozen=True)
38
+ class RetrievalReport:
39
+ """What the local fetch delivered and what it did not.
40
+
41
+ Attributes:
42
+ unpacked: The relative paths written beneath the destination, in archive order.
43
+ not_retrieved: A description per item that was not retrieved, naming the stage
44
+ at which it failed. Empty when the whole tree was delivered.
45
+ """
46
+
47
+ unpacked: tuple[str, ...]
48
+ not_retrieved: tuple[str, ...]
49
+
50
+
51
+ def _resolved_within(destination: Path, entry_name: str) -> Path:
52
+ """Resolve an entry path under the destination, refusing any escape.
53
+
54
+ Args:
55
+ destination: The already-resolved destination directory.
56
+ entry_name: The archive entry's relative path.
57
+
58
+ Returns:
59
+ The resolved target path, guaranteed to lie inside destination.
60
+
61
+ Raises:
62
+ ExportUnpackError: If the entry is absolute or resolves outside destination.
63
+ """
64
+ if entry_name.startswith("/") or Path(entry_name).is_absolute():
65
+ raise ExportUnpackError(entry_name, "entry path is absolute")
66
+ target = (destination / entry_name).resolve()
67
+ if target != destination and destination not in target.parents:
68
+ raise ExportUnpackError(entry_name, "entry path would escape the destination")
69
+ return target
70
+
71
+
72
+ def unpack_verified_archive(
73
+ archive: VerifiedFile,
74
+ destination: str | Path,
75
+ ) -> list[str]:
76
+ """Expand a verified gzip-tar archive under destination, preserving relative paths.
77
+
78
+ Every entry is validated before any byte is written, so a refusal leaves nothing
79
+ behind. Entries are reproduced under their original names and relative positions.
80
+
81
+ Args:
82
+ archive: The verified archive, carrying its plan-relative name and bytes.
83
+ destination: The local directory under which the tree is reproduced; it is
84
+ created if absent.
85
+
86
+ Returns:
87
+ The relative paths of the files written, in archive order.
88
+
89
+ Raises:
90
+ ExportUnpackError: If the bytes are not a readable gzip tar, or any entry is
91
+ absolute, escapes the destination, or is neither a regular file nor a
92
+ directory.
93
+ """
94
+ root = Path(destination).resolve()
95
+ try:
96
+ with tarfile.open(fileobj=io.BytesIO(archive.content), mode="r:gz") as tar:
97
+ members = tar.getmembers()
98
+ for member in members:
99
+ if not (member.isfile() or member.isdir()):
100
+ raise ExportUnpackError(
101
+ member.name, "entry is neither a regular file nor a directory"
102
+ )
103
+ _resolved_within(root, member.name)
104
+
105
+ root.mkdir(parents=True, exist_ok=True)
106
+ written: list[str] = []
107
+ for member in members:
108
+ target = _resolved_within(root, member.name)
109
+ if member.isdir():
110
+ target.mkdir(parents=True, exist_ok=True)
111
+ continue
112
+ target.parent.mkdir(parents=True, exist_ok=True)
113
+ extracted = tar.extractfile(member)
114
+ if extracted is None:
115
+ raise ExportUnpackError(member.name, "entry carries no readable content")
116
+ with extracted:
117
+ target.write_bytes(extracted.read())
118
+ written.append(member.name)
119
+ except tarfile.TarError as error:
120
+ raise ExportUnpackError(archive.filename, f"archive is not a readable gzip tar: {error}") from error
121
+ return written
122
+
123
+
124
+ def build_retrieval_report(
125
+ unpacked: Sequence[str],
126
+ not_retrieved: Sequence[str] = (),
127
+ ) -> RetrievalReport:
128
+ """Build the report of what the local fetch delivered.
129
+
130
+ Args:
131
+ unpacked: The relative paths written beneath the destination.
132
+ not_retrieved: Descriptions of what was not retrieved, each naming the stage at
133
+ which it failed; empty when the whole tree was delivered.
134
+
135
+ Returns:
136
+ The RetrievalReport carrying both sets.
137
+ """
138
+ return RetrievalReport(
139
+ unpacked=tuple(unpacked),
140
+ not_retrieved=tuple(not_retrieved),
141
+ )
142
+
143
+
144
+ __all__ = [
145
+ "ExportUnpackError",
146
+ "RetrievalReport",
147
+ "build_retrieval_report",
148
+ "unpack_verified_archive",
149
+ ]
@@ -0,0 +1,101 @@
1
+ """Whole-file digest verification gate for Path B export retrieval.
2
+
3
+ Nothing retrieved from the server may reach the caller's filesystem before its whole-file
4
+ sha256 has been recomputed locally and matched against the digest the server declared for
5
+ that file. This module is that gate: it turns a reassembled buffer into a verified file, or
6
+ raises the delivery contract's distinct integrity error. The error is deliberately its own
7
+ type so that a digest mismatch is never confused with a transport or protocol failure.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import hashlib
13
+ from dataclasses import dataclass
14
+ from typing import Iterable
15
+
16
+ from plan_manager_client.export_fetch_chunks import ReassembledFile
17
+
18
+
19
+ class ExportIntegrityError(RuntimeError):
20
+ """Raised when a reassembled file's sha256 does not match its declared digest.
21
+
22
+ This is the distinct integrity error of the delivery contract: it aborts retrieval of
23
+ the offending file and is raised for no other kind of failure. It names only the
24
+ plan-relative file name and the two digests: it never carries connection, credential,
25
+ or server-side path information.
26
+ """
27
+
28
+ def __init__(self, filename: str, expected_sha256: str, actual_sha256: str) -> None:
29
+ super().__init__(
30
+ f"integrity check failed for {filename!r}: "
31
+ f"expected sha256 {expected_sha256}, computed {actual_sha256}"
32
+ )
33
+ self.filename = filename
34
+ self.expected_sha256 = expected_sha256
35
+ self.actual_sha256 = actual_sha256
36
+
37
+
38
+ @dataclass(frozen=True)
39
+ class VerifiedFile:
40
+ """One exported file whose content matched the digest the server declared for it.
41
+
42
+ Attributes:
43
+ filename: The plan-relative name of the file, unchanged from retrieval.
44
+ content: The verified bytes of the file, eligible for persistence.
45
+ """
46
+
47
+ filename: str
48
+ content: bytes
49
+
50
+
51
+ def verify_reassembled_file(reassembled: ReassembledFile) -> VerifiedFile:
52
+ """Verify one reassembled file's whole-file sha256 against its declared digest.
53
+
54
+ Args:
55
+ reassembled: The in-memory file carrying the server-declared digest.
56
+
57
+ Returns:
58
+ The VerifiedFile, eligible for persistence, when the digests match.
59
+
60
+ Raises:
61
+ ExportIntegrityError: When the computed digest differs from the declared one.
62
+ """
63
+ actual_sha256 = hashlib.sha256(reassembled.content).hexdigest()
64
+ if actual_sha256 != reassembled.declared_sha256:
65
+ raise ExportIntegrityError(
66
+ reassembled.filename, reassembled.declared_sha256, actual_sha256
67
+ )
68
+ return VerifiedFile(filename=reassembled.filename, content=reassembled.content)
69
+
70
+
71
+ def verify_reassembled_files(
72
+ reassembled: Iterable[ReassembledFile],
73
+ ) -> tuple[list[VerifiedFile], list[ExportIntegrityError]]:
74
+ """Verify every reassembled file independently, collecting both outcomes.
75
+
76
+ A mismatch on one file does not stop the others from being verified, so the caller
77
+ can report precisely which files passed and which did not.
78
+
79
+ Args:
80
+ reassembled: The in-memory files to verify.
81
+
82
+ Returns:
83
+ A tuple of the files that verified and the integrity errors raised for those
84
+ that did not, each list in the order the inputs were given.
85
+ """
86
+ verified: list[VerifiedFile] = []
87
+ failures: list[ExportIntegrityError] = []
88
+ for item in reassembled:
89
+ try:
90
+ verified.append(verify_reassembled_file(item))
91
+ except ExportIntegrityError as error:
92
+ failures.append(error)
93
+ return verified, failures
94
+
95
+
96
+ __all__ = [
97
+ "ExportIntegrityError",
98
+ "VerifiedFile",
99
+ "verify_reassembled_file",
100
+ "verify_reassembled_files",
101
+ ]
@@ -0,0 +1,224 @@
1
+ """Agent-configuration data-layer command-family facade mixin (CR-5a: tool/toolset/role/provider/model/role-model-binding/invocation-profile entities and their resolve commands).
2
+
3
+ Author: Vasiliy Zdanovskiy
4
+ email: vasilyvz@gmail.com
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Any
10
+
11
+
12
+ class AgentConfigCommandsMixin:
13
+ """One method per plan_manager command in the CR-5a agent-configuration data-layer family.
14
+
15
+ Covers the seven entity families introduced by CR-5a -- tool (C-001), toolset
16
+ (C-002) plus its ordered membership, role (C-003), provider (C-004), model
17
+ (C-005), role-model binding (C-006), and invocation profile (C-008) -- together
18
+ with the three resolve commands that read the level-indirected configuration for
19
+ a concrete plan/step/role target: role_model_resolve (C-006), step_assignment_resolve
20
+ (C-007), and invocation_profile_resolve (C-008). Assumes it is mixed into a
21
+ class that also inherits plan_manager_client.dispatch._CommandDispatchMixin,
22
+ which supplies the coroutine
23
+ async def _call(self, command: str, params: dict[str, Any] | None = None) -> Any
24
+ used by every method below.
25
+ """
26
+
27
+ async def tool_create(self, **params: Any) -> Any:
28
+ """Create a new tool instrument record (C-001): a server reference, a command name, and a pinned option set."""
29
+ return await self._call("tool_create", params)
30
+
31
+ async def tool_get(self, **params: Any) -> Any:
32
+ """Retrieve a single tool instrument record (C-001) by its tool identifier."""
33
+ return await self._call("tool_get", params)
34
+
35
+ async def tool_update(self, **params: Any) -> Any:
36
+ """Patch the mutable fields of an existing tool instrument record (C-001) in place."""
37
+ return await self._call("tool_update", params)
38
+
39
+ async def tool_list(self, **params: Any) -> Any:
40
+ """List a paginated page of tool instrument records (C-001) filtered by name."""
41
+ return await self._call("tool_list", params)
42
+
43
+ async def tool_delete(self, **params: Any) -> Any:
44
+ """Delete a tool instrument record (C-001): soft by default, hard=true gated by the inbound-reference integrity check, dry_run previews."""
45
+ return await self._call("tool_delete", params)
46
+
47
+ async def toolset_create(self, **params: Any) -> Any:
48
+ """Create a new toolset record (C-002): a named, ordered set of tool references describing the equipment list of one kind of work."""
49
+ return await self._call("toolset_create", params)
50
+
51
+ async def toolset_get(self, **params: Any) -> Any:
52
+ """Retrieve a single toolset record (C-002) by its toolset identifier."""
53
+ return await self._call("toolset_get", params)
54
+
55
+ async def toolset_update(self, **params: Any) -> Any:
56
+ """Patch the mutable fields of an existing toolset record (C-002) in place."""
57
+ return await self._call("toolset_update", params)
58
+
59
+ async def toolset_list(self, **params: Any) -> Any:
60
+ """List a paginated page of toolset records (C-002) filtered by name."""
61
+ return await self._call("toolset_list", params)
62
+
63
+ async def toolset_delete(self, **params: Any) -> Any:
64
+ """Delete a toolset record (C-002): soft by default, hard=true gated by the inbound-reference integrity check, dry_run previews."""
65
+ return await self._call("toolset_delete", params)
66
+
67
+ async def toolset_member_add(self, **params: Any) -> Any:
68
+ """Add an ordered tool reference to a toolset (C-002 uses C-001): attaches one tool_uuid at a given position without embedding the Tool record."""
69
+ return await self._call("toolset_member_add", params)
70
+
71
+ async def toolset_member_remove(self, **params: Any) -> Any:
72
+ """Remove (soft-delete) one ordered tool-reference membership from a toolset (C-002 uses C-001)."""
73
+ return await self._call("toolset_member_remove", params)
74
+
75
+ async def toolset_member_get(self, **params: Any) -> Any:
76
+ """Read one toolset membership by its id (parameters passed through; the server validates them)."""
77
+ return await self._call("toolset_member_get", params)
78
+
79
+ async def toolset_member_list(self, **params: Any) -> Any:
80
+ """List toolset memberships with filters and pagination (parameters passed through; the server validates them)."""
81
+ return await self._call("toolset_member_list", params)
82
+
83
+ async def toolset_member_delete(self, **params: Any) -> Any:
84
+ """Delete a toolset membership: mark by default, hard=true removes a marked membership, dry_run previews (parameters passed through; the server validates them)."""
85
+ return await self._call("toolset_member_delete", params)
86
+
87
+ async def role_create(self, **params: Any) -> Any:
88
+ """Create a new role record (C-003): a first-class stored entity naming who the agent is."""
89
+ return await self._call("role_create", params)
90
+
91
+ async def role_get(self, **params: Any) -> Any:
92
+ """Retrieve a single role record (C-003) by its role identifier."""
93
+ return await self._call("role_get", params)
94
+
95
+ async def role_update(self, **params: Any) -> Any:
96
+ """Patch the mutable description field of an existing role record (C-003) in place."""
97
+ return await self._call("role_update", params)
98
+
99
+ async def role_list(self, **params: Any) -> Any:
100
+ """List a paginated page of role records (C-003)."""
101
+ return await self._call("role_list", params)
102
+
103
+ async def role_delete(self, **params: Any) -> Any:
104
+ """Delete a role record (C-003): soft by default, hard=true gated by the inbound-reference integrity check, dry_run previews."""
105
+ return await self._call("role_delete", params)
106
+
107
+ async def provider_create(self, **params: Any) -> Any:
108
+ """Create a new provider record (C-004): the source that serves a model, carrying its type, hardware ownership, activity status, and billing notes."""
109
+ return await self._call("provider_create", params)
110
+
111
+ async def provider_get(self, **params: Any) -> Any:
112
+ """Retrieve a single provider record (C-004) by its provider identifier."""
113
+ return await self._call("provider_get", params)
114
+
115
+ async def provider_update(self, **params: Any) -> Any:
116
+ """Patch the mutable fields of an existing provider record (C-004) in place."""
117
+ return await self._call("provider_update", params)
118
+
119
+ async def provider_list(self, **params: Any) -> Any:
120
+ """List a paginated page of provider records (C-004) filtered by type and status."""
121
+ return await self._call("provider_list", params)
122
+
123
+ async def provider_delete(self, **params: Any) -> Any:
124
+ """Delete a provider record (C-004): soft by default, hard=true gated by the inbound-reference integrity check, dry_run previews."""
125
+ return await self._call("provider_delete", params)
126
+
127
+ async def provider_set_status(self, **params: Any) -> Any:
128
+ """Switch a provider's activity status in a single call (C-004): the dedicated switching-axis operation, distinct from the general provider_update."""
129
+ return await self._call("provider_set_status", params)
130
+
131
+ async def model_create(self, **params: Any) -> Any:
132
+ """Create a new invocable model record (C-005): name, provider reference, capability level, and execution mode."""
133
+ return await self._call("model_create", params)
134
+
135
+ async def model_get(self, **params: Any) -> Any:
136
+ """Retrieve a single invocable model record (C-005) by its model identifier."""
137
+ return await self._call("model_get", params)
138
+
139
+ async def model_update(self, **params: Any) -> Any:
140
+ """Patch the mutable fields of an existing invocable model record (C-005) in place."""
141
+ return await self._call("model_update", params)
142
+
143
+ async def model_list(self, **params: Any) -> Any:
144
+ """List a paginated page of invocable model records (C-005) filtered by provider, level, and execution mode."""
145
+ return await self._call("model_list", params)
146
+
147
+ async def model_delete(self, **params: Any) -> Any:
148
+ """Delete an invocable model record (C-005): soft by default, hard=true gated by the inbound-reference integrity check, dry_run previews."""
149
+ return await self._call("model_delete", params)
150
+
151
+ async def role_model_binding_create(self, **params: Any) -> Any:
152
+ """Create a role-model level binding record (C-006) for the given role and required model level."""
153
+ return await self._call("role_model_binding_create", params)
154
+
155
+ async def role_model_binding_get(self, **params: Any) -> Any:
156
+ """Retrieve a single role-model binding record (C-006) by its binding identifier."""
157
+ return await self._call("role_model_binding_get", params)
158
+
159
+ async def role_model_binding_update(self, **params: Any) -> Any:
160
+ """Patch the mutable fields of an existing role-model binding record (C-006) in place."""
161
+ return await self._call("role_model_binding_update", params)
162
+
163
+ async def role_model_binding_list(self, **params: Any) -> Any:
164
+ """List a paginated page of role-model binding records (C-006) filtered by role."""
165
+ return await self._call("role_model_binding_list", params)
166
+
167
+ async def role_model_binding_delete(self, **params: Any) -> Any:
168
+ """Delete a role-model binding record (C-006): soft by default, hard=true gated by the inbound-reference integrity check, dry_run previews."""
169
+ return await self._call("role_model_binding_delete", params)
170
+
171
+ async def invocation_profile_create(self, **params: Any) -> Any:
172
+ """Create an invocation profile runtime-configuration record (C-008) for the given scope."""
173
+ return await self._call("invocation_profile_create", params)
174
+
175
+ async def invocation_profile_get(self, **params: Any) -> Any:
176
+ """Retrieve a single invocation profile record (C-008) by its profile identifier."""
177
+ return await self._call("invocation_profile_get", params)
178
+
179
+ async def invocation_profile_update(self, **params: Any) -> Any:
180
+ """Patch the mutable informational fields of an existing invocation profile record (C-008) in place."""
181
+ return await self._call("invocation_profile_update", params)
182
+
183
+ async def invocation_profile_list(self, **params: Any) -> Any:
184
+ """List a paginated page of invocation profile records (C-008) filtered by plan, scope, and role."""
185
+ return await self._call("invocation_profile_list", params)
186
+
187
+ async def invocation_profile_delete(self, **params: Any) -> Any:
188
+ """Delete an invocation profile record (C-008): soft by default, hard=true gated by the inbound-reference integrity check, dry_run previews."""
189
+ return await self._call("invocation_profile_delete", params)
190
+
191
+ async def step_assignment_create(self, **params: Any) -> Any:
192
+ """Create a step assignment runtime record (C-007) for the given scope."""
193
+ return await self._call("step_assignment_create", params)
194
+
195
+ async def step_assignment_get(self, **params: Any) -> Any:
196
+ """Retrieve a single step assignment record (C-007) by its assignment identifier."""
197
+ return await self._call("step_assignment_get", params)
198
+
199
+ async def step_assignment_update(self, **params: Any) -> Any:
200
+ """Patch the mutable fields of an existing step assignment record (C-007) in place."""
201
+ return await self._call("step_assignment_update", params)
202
+
203
+ async def step_assignment_list(self, **params: Any) -> Any:
204
+ """List a paginated page of step assignment records (C-007) filtered by plan, scope, and role."""
205
+ return await self._call("step_assignment_list", params)
206
+
207
+ async def step_assignment_delete(self, **params: Any) -> Any:
208
+ """Delete a step assignment record (C-007): soft by default, hard=true gated by the inbound-reference integrity check, dry_run previews."""
209
+ return await self._call("step_assignment_delete", params)
210
+
211
+ async def role_model_resolve(self, **params: Any) -> Any:
212
+ """Resolve the effective (provider, model) for a role from active providers, explicit model bindings, and the manual role-model level relation (C-006)."""
213
+ return await self._call("role_model_resolve", params)
214
+
215
+ async def step_assignment_resolve(self, **params: Any) -> Any:
216
+ """Resolve the effective per-step role and toolset assignment along the six-scope specificity ladder (C-007)."""
217
+ return await self._call("step_assignment_resolve", params)
218
+
219
+ async def invocation_profile_resolve(self, **params: Any) -> Any:
220
+ """Resolve the effective invocation profile along the six-scope specificity ladder (C-008)."""
221
+ return await self._call("invocation_profile_resolve", params)
222
+
223
+
224
+ __all__ = ["AgentConfigCommandsMixin"]