Record delivery choices and protected preflight preparation
This commit is contained in:
parent
9f1ea088ef
commit
03f10eb91c
12 changed files with 456 additions and 16 deletions
|
|
@ -0,0 +1,65 @@
|
|||
# Actual interactive assignment launch — owner finding
|
||||
|
||||
This is conditional source preparation. The founder's current choice between the
|
||||
actual interactive Codex assignment and native-worker-first acceptance is pending;
|
||||
this document selects neither and does not treat one as proof of the other.
|
||||
|
||||
## Existing reusable surfaces
|
||||
|
||||
- sand-boxer owns capability.execution.sandbox-provision: provisioning, protected
|
||||
runtime mounts, execution environment, egress and teardown. Its current bwrap
|
||||
runner accepts bounded stdin and uses subprocess.communicate; this is command
|
||||
execution, not an admitted long-lived interactive Codex session transport.
|
||||
- glas-harness owns capability.execution.profile-harness: profile resolution,
|
||||
assignment attribution, sandbox creation, rein dispatch and cleanup. Its gateway
|
||||
executes one request. The committed concrete rein descriptors are Claude Code
|
||||
unattended and open-weight; no Codex interactive descriptor or admission receipt
|
||||
was found in these source catalogs.
|
||||
- rein-aharness owns capability.execution.scheduled-agent-rein: Activity Core claim,
|
||||
heartbeat and routing plus repository acceptance. Its f599 offline startup is
|
||||
proven; it does not launch this current interactive Codex assignment.
|
||||
- state-hub/scripts/configure-codex.sh registers local service/tool settings. It
|
||||
does not isolate the Codex parent process or admit a restricted execution host.
|
||||
|
||||
## Concrete next owner return, if interactive scope is selected
|
||||
|
||||
Lead: glas-harness for the exact assignment launch/admission contract, with
|
||||
sand-boxer delivering the executable boundary and railiance-infra the separate
|
||||
account. The owner of the CURRENT frontend/executor attachment must be identified
|
||||
explicitly: the local catalog does not establish who can reattach this managed
|
||||
interactive session. Do not silently replace it with a new CLI session.
|
||||
|
||||
Return one versioned launch packet containing:
|
||||
|
||||
1. The selected frontend/transport and actual launch point before the first agent
|
||||
tool: pinned executable/profile, UID, workspace, private HOME/state, inherited
|
||||
environment allowlist, allowed tool transports and explicit supervisor identity.
|
||||
2. A way to start the entire interactive executor/session under that boundary,
|
||||
or a documented inability of the current frontend to attach to it. No parent
|
||||
process with host admin authority may remain agent-callable. For a selected CLI
|
||||
frontend, implement/test initial launch inside the owner boundary; for a selected
|
||||
managed frontend, obtain its supported restricted-executor attachment contract.
|
||||
Neither path is delivered by a child sandbox launched from this current session.
|
||||
3. Positive interactive start/input/output and cancellation/teardown proof, followed
|
||||
by reconnect/resume evidence that uses the same restricted identity and reviewed
|
||||
profile. No fallback to the old host executor on refusal or reconnect failure.
|
||||
4. A complete tool-path inventory: shell/files, SSH agents, containers, GitOps write
|
||||
paths, plugins/MCP and remote connectors. Remove or route all privileged external
|
||||
paths to a separately owned exact-approval executor; filesystem isolation alone
|
||||
cannot restrict an external connector's independent authority.
|
||||
5. Native proof from that assignment: original and per-namespace auth denials,
|
||||
old host/admin paths denied, sanitized exact approved action succeeds, and
|
||||
changed/unapproved/expired/replayed actions refuse. Bind all receipts to that
|
||||
assignment and actual runtime revision. Keep autopilot disabled.
|
||||
|
||||
No new general supervisor service is required. The first missing deliverable is
|
||||
this owner-supported initial launch/attachment, not another disposable bwrap test.
|
||||
The native-worker-first path may reuse existing f599/Glas/rein work but must retain
|
||||
the interactive migration gap as unfinished unless the founder explicitly changes
|
||||
that scope. Provider login/credential work is outside this source preparation.
|
||||
|
||||
Official context, not a claim about this session: OpenAI's self-hosted environment
|
||||
documentation describes an executor started inside the selected isolated environment
|
||||
and keeping application credentials outside it. That reinforces the launch-point
|
||||
requirement but does not prove this frontend uses that API or supports reattachment:
|
||||
https://developers.openai.com/api/docs/guides/agents-api/environments/self-hosted
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
# Review-only privileged preflight, CUST73 existing-host addendum
|
||||
|
||||
Prepared but NOT executed. This is a separate proposed read-only action; it grants
|
||||
no unit replacement, sudo-policy change, credential issuance or service restart.
|
||||
The original credential-boundary revision2 and existing-host addendum are unchanged.
|
||||
|
||||
Owner: railiance-cluster (effective k3s configuration); attended privileged executor
|
||||
and execution approval remain the supervisor's responsibility. This script requires
|
||||
root only to inspect protected service environment/config selectors and the active
|
||||
k3s process environment. Protected values exist transiently in its memory; they
|
||||
are never printed, hashed into receipts, logged, evaluated or forwarded elsewhere.
|
||||
Only fixed boolean predicates and presence of four explicitly named configuration
|
||||
keys leave the process. Any exception prints one constant refusal, never traceback.
|
||||
|
||||
After review, the human owner must place the exact reviewed script in a private
|
||||
root-owned non-agent-writable directory and verify its SHA256 against SHA256SUMS.
|
||||
Do not execute an agent-writable /tmp file under sudo or pipe a live checkout into
|
||||
root Python. Proposed owner command, on the verified Railiance01 host:
|
||||
|
||||
```sh
|
||||
sudo -- /usr/bin/python3 -I -B /root/cust73-preflight/preflight.py
|
||||
```
|
||||
|
||||
No command above has been executed by this worker. Stage/install/approval are still
|
||||
needed before the command is usable. No password, token or environment value is to
|
||||
be pasted into the transcript. The script makes no writes, reload, restart, network
|
||||
request or credential issuance call. Its only subprocess is read-only systemctl show
|
||||
with fixed service and property names; all subprocess output is captured.
|
||||
|
||||
The checks bind the expected hashed machine identity, exact original unit digest
|
||||
and root-owned file identity, stable main-process PID/starttime, absent unit drop-ins,
|
||||
no pending reload, mode644 launch argument, no CLI config selector, and only known
|
||||
EnvironmentFile sources. It parses a deliberately conservative simple assignment
|
||||
subset; unsupported quoting/continuations, duplicate keys, unknown source paths,
|
||||
selector presence, unreadable source or a changed unit/process require owner review.
|
||||
It never follows a protected selector's value into another file. Default config and
|
||||
config drop-ins must be absent for this exact baseline packet to pass.
|
||||
|
||||
A pass settles only the protected-input predicates for the prepared mode600 unit
|
||||
change. It does not authorize that change or prove workload health, account isolation,
|
||||
SSH principal denial, interactive assignment admission, native-model acceptance or
|
||||
supervised privileged execution. A present selector is an owner inspection gate,
|
||||
not an instruction to disclose its value or to clear it automatically.
|
||||
|
||||
Validation: eight fixture tests pass, including sensitive-value non-emission,
|
||||
selector detection, complex/duplicate syntax refusal, non-root refusal before reads,
|
||||
and constant-output exception handling. There is no live privileged receipt yet.
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
3326079f7dd2662d08a2fb019394a897892abfafa479b82b9a01b1f0c0b98838 INTERACTIVE-OWNER-DELIVERABLE.md
|
||||
66f76ae06ea5d3554c08810dee924fdd9a977798a56a7adfd680b06a7a9c338f OPERATOR.md
|
||||
0e9058355cb1357e32090d6de6c9f6e6694069335eb3ca54701e0be48d393990 preflight.py
|
||||
83a50903f7c9d05fb721d21585184f0f9965f09b0445cb9057760a726e6c54de source-pins.json
|
||||
738beccd2b5fe679926d243b25c7b77a87de5879f5e1cfd0e871500fc8c43ac3 test_preflight.py
|
||||
04c636a085f0440d0b8a3760e41afedd4703360dec128b119ef32aceb94bf22d validation.json
|
||||
139
docs/changes/CUST-WP-0073/privileged-preflight-r1/preflight.py
Normal file
139
docs/changes/CUST-WP-0073/privileged-preflight-r1/preflight.py
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Privileged READ ONLY k3s selector preflight; emit fixed predicates, never values."""
|
||||
import datetime
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import stat
|
||||
import subprocess
|
||||
|
||||
UNIT_SHA = 'b5398c385430ce225faadc1f3724597fe4b05dd999a04b8abbca64d4b0d68c0e'
|
||||
MACHINE_SHA = 'fe7367a60951922122af7ba18ed2cde65c7e0a232bbedfb045cb575de2094c2f'
|
||||
ENV_FILES = ('/etc/default/k3s', '/etc/sysconfig/k3s', '/etc/systemd/system/k3s.service.env')
|
||||
SELECTORS = ('K3S_CONFIG_FILE', 'K3S_KUBECONFIG_MODE', 'K3S_KUBECONFIG_OUTPUT', 'K3S_KUBECONFIG_GROUP')
|
||||
|
||||
|
||||
def read_fixed(path, max_bytes=1024*1024):
|
||||
fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW)
|
||||
try:
|
||||
before = os.fstat(fd)
|
||||
if not stat.S_ISREG(before.st_mode) or before.st_uid != 0 or before.st_mode & 0o022:
|
||||
raise ValueError('file_custody')
|
||||
with os.fdopen(os.dup(fd), 'rb') as stream:
|
||||
data = stream.read(max_bytes+1)
|
||||
after = os.fstat(fd)
|
||||
if len(data) > max_bytes or (before.st_size,before.st_mtime_ns,before.st_ctime_ns) != (after.st_size,after.st_mtime_ns,after.st_ctime_ns):
|
||||
raise ValueError('file_changed_or_oversized')
|
||||
return data, before
|
||||
finally:
|
||||
os.close(fd)
|
||||
|
||||
|
||||
def env_predicates(raw, *, process=False):
|
||||
"""Only key presence leaves this function. No shell evaluation or raw errors."""
|
||||
found = set()
|
||||
valid = True
|
||||
for line in (raw.split(b'\0') if process else raw.splitlines()):
|
||||
line = line.strip()
|
||||
if not line or (not process and line.startswith((b'#',b';'))):
|
||||
continue
|
||||
match = re.fullmatch(rb'([A-Za-z_][A-Za-z_0-9]*)=(.*)',line)
|
||||
if not match:
|
||||
valid = False
|
||||
continue
|
||||
name = match.group(1).decode('ascii')
|
||||
if name in found:
|
||||
valid = False
|
||||
found.add(name)
|
||||
if not process:
|
||||
# Conservative subset of systemd EnvironmentFile, not a shell parser.
|
||||
# Complex/multiline/escape forms require owner review, never guess.
|
||||
value = match.group(2)
|
||||
if b'\\' in value or value.count(b'"') not in (0,2) or value.count(b"'") not in (0,2):
|
||||
valid = False
|
||||
return {'syntax_supported': valid,
|
||||
'selectors_absent': all(key not in found for key in SELECTORS),
|
||||
'selector_presence': {key:key in found for key in SELECTORS}}
|
||||
|
||||
|
||||
def show(prop):
|
||||
result = subprocess.run(['/usr/bin/systemctl','show','k3s','--property='+prop,'--value'],
|
||||
capture_output=True,timeout=10,check=False)
|
||||
if result.returncode:
|
||||
raise ValueError('systemd_read_failed')
|
||||
return result.stdout.strip()
|
||||
|
||||
|
||||
def inspect():
|
||||
if os.geteuid() != 0:
|
||||
return {'status':'refused_requires_attended_root','read_only':True,'passed':False}
|
||||
checks = {}
|
||||
checks['expected_machine'] = hashlib.sha256(Path('/etc/machine-id').read_bytes()).hexdigest() == MACHINE_SHA
|
||||
if not checks['expected_machine']:
|
||||
return {'status':'refused_target','read_only':True,'passed':False,'checks':checks}
|
||||
unit, meta = read_fixed('/etc/systemd/system/k3s.service')
|
||||
checks['pinned_unit'] = hashlib.sha256(unit).hexdigest() == UNIT_SHA
|
||||
checks['unit_identity'] = (meta.st_dev,meta.st_ino,meta.st_gid,stat.S_IMODE(meta.st_mode),meta.st_nlink)==(2049,7875,0,0o644,1)
|
||||
if not all(checks.values()):
|
||||
return {'status':'refused_unit_drift','read_only':True,'passed':False,'checks':checks}
|
||||
checks['fragment_expected'] = show('FragmentPath') == b'/etc/systemd/system/k3s.service'
|
||||
checks['no_dropins'] = show('DropInPaths') == b''
|
||||
checks['no_pending_reload'] = show('NeedDaemonReload') == b'no'
|
||||
checks['active'] = show('ActiveState') == b'active' and show('SubState') == b'running'
|
||||
pid = show('MainPID')
|
||||
if not re.fullmatch(rb'[1-9][0-9]*',pid):
|
||||
raise ValueError('main_pid_unavailable')
|
||||
proc = Path('/proc')/pid.decode('ascii')
|
||||
proc_stat = (proc/'stat').read_bytes()
|
||||
start = proc_stat.rsplit(b')',1)[1].split()[19]
|
||||
# Read only this bound process environment. Never emit raw bytes or digest.
|
||||
environment = (proc/'environ').read_bytes()
|
||||
effective = env_predicates(environment,process=True)
|
||||
checks['effective_env_simple'] = effective['syntax_supported']
|
||||
checks['effective_selectors_absent'] = effective['selectors_absent']
|
||||
argv = (proc/'cmdline').read_bytes().split(b'\0')
|
||||
checks['effective_mode644'] = argv.count(b'--write-kubeconfig-mode=644') == 1
|
||||
checks['no_cli_config_selector'] = not any(x in (b'-c',b'--config') or x.startswith(b'--config=') or (x.startswith(b'-c') and len(x)>2) for x in argv)
|
||||
refs=[]
|
||||
for match in re.finditer(rb'^EnvironmentFile=([^\r\n]+)',unit,re.M):
|
||||
value=match.group(1).strip();optional=value.startswith(b'-');value=value.lstrip(b'-')
|
||||
if value.decode('utf-8') not in ENV_FILES:
|
||||
raise ValueError('unknown_environment_source')
|
||||
refs.append((value.decode('utf-8'),optional))
|
||||
checks['known_environment_sources'] = len(refs)>0 and len({x[0] for x in refs})==len(refs)
|
||||
file_results={}
|
||||
for path,optional in refs:
|
||||
try:
|
||||
raw, st = read_fixed(path)
|
||||
predicates=env_predicates(raw)
|
||||
file_results[path]={'present':True,**predicates}
|
||||
except FileNotFoundError:
|
||||
file_results[path]={'present':False,'syntax_supported':optional,'selectors_absent':optional}
|
||||
checks['environment_file_'+str(ENV_FILES.index(path))] = file_results[path]['syntax_supported'] and file_results[path]['selectors_absent']
|
||||
checks['default_config_absent'] = not Path('/etc/rancher/k3s/config.yaml').exists()
|
||||
directory=Path('/etc/rancher/k3s/config.yaml.d')
|
||||
checks['default_dropins_absent'] = not directory.exists() or (directory.is_dir() and not directory.is_symlink() and not any(directory.iterdir()))
|
||||
# Revalidate identity to refuse PID reuse/config replacement during inspection.
|
||||
checks['same_process'] = show('MainPID') == pid and (proc/'stat').read_bytes().rsplit(b')',1)[1].split()[19] == start
|
||||
checks['same_unit'] = hashlib.sha256(read_fixed('/etc/systemd/system/k3s.service')[0]).hexdigest()==UNIT_SHA
|
||||
return {'status':'preflight_passed' if all(checks.values()) else 'owner_review_required',
|
||||
'passed':all(checks.values()),'read_only':True,'checks':checks,
|
||||
'environment_files':file_results,'effective_selector_presence':effective['selector_presence'],
|
||||
'target':'railiance01','captured_at_utc':datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
||||
'protected_values_emitted':False,'unit_change_authorized':False,'assignment_admitted':False}
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
receipt=inspect()
|
||||
except BaseException:
|
||||
# No exception text/traceback, even when a parser embeds protected input.
|
||||
receipt={'status':'preflight_refused','passed':False,'read_only':True,'protected_values_emitted':False}
|
||||
print(json.dumps(receipt,sort_keys=True))
|
||||
return 0 if receipt['passed'] else 1
|
||||
|
||||
|
||||
if __name__=='__main__':
|
||||
raise SystemExit(main())
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"glas-harness": {
|
||||
"head": "a19bff487d41f18c58a8fd713f6d4e04c3a218d5",
|
||||
"files": {
|
||||
"src/glas_harness/gateway.py": "5e998bfa53ade5dafbae799dc04b77031b6414e2af49797f45727d59d7397d42",
|
||||
"registry/reins/rein-aharness.yaml": "8cdbe7b5ee45e2fb5ca2ed089cab377580034dc261c46bfcf889bddb68cf3da0",
|
||||
"SCOPE.md": "dbc1c24fee17a9fccf7bcae0162897980a5675877f69f3c968d7efd271f4be2e"
|
||||
}
|
||||
},
|
||||
"sand-boxer": {
|
||||
"head": "c7c310266aba96ab7dd03cd8bfcaa65f22a3b66e",
|
||||
"files": {
|
||||
"src/sandboxer/extensions/bwrap_runner.py": "c83cdd2758bae58cd5d5694f5d6752736f59d479cfc6e9f0f8841c1f5596cba6",
|
||||
"src/sandboxer/core/manager.py": "213884776cdd9aeab1daf02d178b95f4b70b4fe62af2d87df811f0ba129fa9e1"
|
||||
}
|
||||
},
|
||||
"rein-aharness": {
|
||||
"head": "b80096133f9b7ad8688535e06ba253729f3948fa",
|
||||
"files": {
|
||||
"SCOPE.md": "21d3b85a79205541591d139caf5184f4402ee2f53dce562f3a7a1da1dec62683"
|
||||
}
|
||||
},
|
||||
"state-hub": {
|
||||
"head": "39b51a21a51204675cc46d4b51c00fddd1851449",
|
||||
"files": {
|
||||
"scripts/configure-codex.sh": "c6e0e2f47f5ff9e34f7b04a887e3e8e942fcd3eb78c5713be9d1eb4d49091c32"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
import contextlib
|
||||
import io
|
||||
import json
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
import preflight
|
||||
|
||||
SENTINEL='synthetic-sensitive-fixture-value'
|
||||
|
||||
class PreflightTests(unittest.TestCase):
|
||||
def test_unrelated_protected_values_never_return(self):
|
||||
result=preflight.env_predicates(('TOKEN='+SENTINEL+'\nOTHER="'+SENTINEL+'"\n').encode())
|
||||
self.assertTrue(result['syntax_supported']);self.assertTrue(result['selectors_absent'])
|
||||
self.assertNotIn(SENTINEL,json.dumps(result))
|
||||
|
||||
def test_each_selector_blocks_without_exposing_value(self):
|
||||
for key in preflight.SELECTORS:
|
||||
result=preflight.env_predicates((key+'='+SENTINEL).encode())
|
||||
self.assertFalse(result['selectors_absent']);self.assertTrue(result['selector_presence'][key])
|
||||
self.assertNotIn(SENTINEL,json.dumps(result))
|
||||
|
||||
def test_effective_process_values_cannot_create_fake_keys(self):
|
||||
result=preflight.env_predicates(('TOKEN=x\nK3S_CONFIG_FILE='+SENTINEL+'\0').encode(),process=True)
|
||||
self.assertFalse(result['syntax_supported'])
|
||||
self.assertNotIn(SENTINEL,json.dumps(result))
|
||||
|
||||
def test_process_selector_detected(self):
|
||||
result=preflight.env_predicates(b'A=x\0K3S_CONFIG_FILE=/hidden\0',process=True)
|
||||
self.assertFalse(result['selectors_absent'])
|
||||
|
||||
def test_complex_syntax_refuses(self):
|
||||
for raw in (b'export KEY=value',b'A="unterminated',b'A=x\\\ny',b'A=x\nA=y'):
|
||||
self.assertFalse(preflight.env_predicates(raw)['syntax_supported'])
|
||||
|
||||
def test_comments_cannot_activate_selector(self):
|
||||
result=preflight.env_predicates(b'# K3S_CONFIG_FILE=/ignored\n; comment\nA=x\n')
|
||||
self.assertTrue(result['selectors_absent'])
|
||||
|
||||
def test_nonroot_refuses_before_any_files(self):
|
||||
with patch.object(preflight.os,'geteuid',return_value=1000),patch.object(preflight,'read_fixed') as read:
|
||||
self.assertEqual(preflight.inspect()['status'],'refused_requires_attended_root')
|
||||
read.assert_not_called()
|
||||
|
||||
def test_exception_text_never_emitted(self):
|
||||
output=io.StringIO()
|
||||
with patch.object(preflight,'inspect',side_effect=ValueError(SENTINEL)),contextlib.redirect_stdout(output):
|
||||
self.assertEqual(preflight.main(),1)
|
||||
self.assertNotIn(SENTINEL,output.getvalue())
|
||||
self.assertFalse(json.loads(output.getvalue())['passed'])
|
||||
|
||||
if __name__=='__main__':unittest.main()
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"captured_at": "2026-10-19T12:31:27.577940+00:00",
|
||||
"tests_passed": 8,
|
||||
"privileged_execution": false,
|
||||
"live_mutations": false,
|
||||
"interactive_scope_selected": false
|
||||
}
|
||||
39
docs/decisions/2026-10-19-custodian-delivery-interview.md
Normal file
39
docs/decisions/2026-10-19-custodian-delivery-interview.md
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# Custodian delivery interview — 2026-10-19
|
||||
|
||||
Decided by Bernd Worsch in this interactive session. Scope: implementation
|
||||
direction for CUST-WP-0071, CUST-WP-0073 and CUST-WP-0079. These answers do not
|
||||
claim runtime acceptance or change the standing live-change/publication rules.
|
||||
|
||||
## Runtime placement
|
||||
|
||||
Selected: “Existing Activity Core worker with pinned scripts and private retained
|
||||
state (recommended)”. Prepare the existing-worker implementation, exact source
|
||||
packaging, fleet input publication and retained-state ownership. Keep both
|
||||
schedules disabled until the reviewed deployment/activation conditions hold.
|
||||
No dedicated worker or second scheduler is required by this selection.
|
||||
|
||||
## Credential-separation priority
|
||||
|
||||
Selected: “Isolate this interactive Codex assignment from admin credentials
|
||||
(recommended)”. The first acceptance target is the actual interactive assignment,
|
||||
including its tool transports, inherited credentials and privileged execution
|
||||
boundary. A sandboxed child process or native Claude-worker rehearsal cannot
|
||||
substitute for that target. Native-worker isolation remains distinct; do not
|
||||
make it an artificial prerequisite unless the actual selected path needs it.
|
||||
|
||||
## Provisional workload sizing
|
||||
|
||||
Asked whether to permit an explicitly agreed synthetic workload for provisional
|
||||
sizing before the attended Bernd/company pilot, with human workflow acceptance
|
||||
remaining separate. Answer: “Let's do one and put the sizing parameters into
|
||||
configs that can be tuned later on.”
|
||||
|
||||
Proceed with a configurable synthetic workload and provisional sizing evidence.
|
||||
Reuse the existing two-user, ordinary-operation p95 ≤ 2 seconds and zero-failure
|
||||
criterion; report document transfer separately. Put corpus size, concurrency,
|
||||
operation mix, duration and other assumptions in owner configuration. Retain
|
||||
actual runtime/measurement provenance and limitations. Unit-test or SQLite
|
||||
fixture timing is not deployed PostgreSQL/container sizing evidence. Do not
|
||||
claim the separate human workflow/recovery acceptance from a synthetic run.
|
||||
|
||||
Hub decision receipt: `8a64ba65-1068-409c-86f8-b9414b29b7b4`.
|
||||
Loading…
Add table
Add a link
Reference in a new issue