Merge canonical main into bottom-panel Stage 2B-1
Integrate main through the Journey/GPU directory-target ratchet (#183).
The public managed-GPU probe additions compose with the v21 wire layer.
Resolve the volatile ledgers to the landed state: advance the canonical
anchor to 7fd646d, remove the completed ratchet lane, preserve the
approved Stage 2 revision-5 recovery facts, and mark the integrated
2B-1 tree as awaiting its from-start full gate rerun.
This commit is contained in:
commit
c8895a85d3
|
|
@ -1,6 +1,6 @@
|
|||
# Active work — cross-machine resume ledger
|
||||
|
||||
**Snapshot: 2026-07-27.** This file records volatile work that has not
|
||||
**Snapshot: 2026-07-28.** This file records volatile work that has not
|
||||
landed on `main`. Read it after `docs/agent-handoff.md`. Remove completed
|
||||
entries when their PR merges; do not let this become a second permanent
|
||||
backlog.
|
||||
|
|
@ -27,12 +27,11 @@ landed regardless of what a lane says.
|
|||
machine-local: `origin` may name this canonical URL, a release mirror,
|
||||
or something else, and therefore has no authority by name alone.
|
||||
- Canonical base at this snapshot:
|
||||
`githubsucks/main` @ `c2d56ff` (Journey Stage 1a #182, which
|
||||
incorporated terminal configuration + copy mode #180, atop Lean 4
|
||||
Stage 4b #181 and the previously recorded landed work; protocol v20).
|
||||
The previous snapshot named `42025e4`, and **the recovery floor
|
||||
advances with it**: the check below now requires `c2d56ff` or newer,
|
||||
so a tree at `42025e4` no longer passes. That is
|
||||
`githubsucks/main` @ `7fd646d` (Journey/GPU directory-target ratchet
|
||||
#183, atop Journey Stage 1a #182 and the previously recorded landed
|
||||
work; protocol v20). The previous snapshot named `c2d56ff`, and **the
|
||||
recovery floor advances with it**: the check below now requires
|
||||
`7fd646d` or newer, so a tree at `c2d56ff` no longer passes. That is
|
||||
deliberate — the floor moves with the base, because a check that
|
||||
accepts an older commit than the declared base passes on a tree the
|
||||
rest of this file does not describe.
|
||||
|
|
@ -71,7 +70,7 @@ git worktree list
|
|||
git status --short --branch
|
||||
```
|
||||
|
||||
The `git log` command must expose `c2d56ff` — the base named above — or a
|
||||
The `git log` command must expose `7fd646d` — the base named above — or a
|
||||
newer intentional main. Keep this threshold and the canonical-base line in
|
||||
step: a recovery check that accepts an older commit than the base it
|
||||
declares canonical will pass on a tree the rest of this file does not
|
||||
|
|
@ -306,18 +305,16 @@ If it does not, stop and repair the remote/fetch configuration.
|
|||
never been enforced. Any CI job that compiles the `crdt` targets has to
|
||||
fix them first or it will be red on arrival.
|
||||
|
||||
## Bottom-panel lane (Arc 7) — 2B-1 GATED; PR HELD ON MAIN RATCHET
|
||||
## Bottom-panel lane (Arc 7) — 2B-1 INTEGRATED; FULL GATES PENDING
|
||||
|
||||
Stage 1, the Stage 2 framing, and Stage 2A are on `main`. Framing
|
||||
revision 5's three-way split of 2B was explicitly approved on
|
||||
2026-07-27. **Stage 2B-1 is implemented, integrated with canonical
|
||||
`main`, and its full matrix has run. No PR is open: one deterministic
|
||||
touched-suite assertion is stale on canonical `main` after #182 and
|
||||
must be corrected separately before this branch can claim a green
|
||||
gate.**
|
||||
`main` @ `7fd646d`, and has no remaining dependency. Its full matrix is
|
||||
being rerun on the integrated tree before a PR opens.**
|
||||
|
||||
- **Stage 2B-1 branch:** `bottom-panel-stage2b`, based on
|
||||
`githubsucks/main` @ `c2d56ff` by merge because review had begun.
|
||||
`githubsucks/main` @ `7fd646d` by merge because review had begun.
|
||||
Recovery: `git fetch githubsucks && git checkout
|
||||
bottom-panel-stage2b`. Everything described through the integration
|
||||
checkpoint is committed and pushed; nothing depends on a worktree or
|
||||
|
|
@ -334,7 +331,7 @@ gate.**
|
|||
statusline version ladder still pinned v20/rejected v21, and Vterm
|
||||
Stage 3 pinned v20 both structurally and in its real headless probe.
|
||||
Those ratchets now expect v21 and, where applicable, reject v22.
|
||||
- **Green evidence on the corrected tree:** formatting and strict
|
||||
- **Pre-integration green evidence at `b9123c2`:** formatting and strict
|
||||
workspace Clippy; library **1,849 passed + 3 ignored default** and
|
||||
**2,034 passed + 4 ignored CRDT**; bottom-panel Stage 1 / 2A / 2B-1
|
||||
**46 / 17 / 15**; folding Stage 2 **48**; GPU font **11**; statusline
|
||||
|
|
@ -347,16 +344,12 @@ gate.**
|
|||
`m8_1_acceptance::read_dir_supersede_cancels_in_flight_predecessor`;
|
||||
the exact pin, its full 10-test target, and the complete workspace
|
||||
rerun all passed.
|
||||
- **Sole deterministic red — reproduced unchanged on canonical
|
||||
`main`:** `gpu_initial_target_acceptance` is **13/14**, failing
|
||||
`malformed_or_unloadable_targets_fail_closed_without_poisoning_the_daemon`.
|
||||
Its invalid-target table still includes `"."` and demands only a
|
||||
failure result, while #182 deliberately made a directory target valid
|
||||
and therefore sends the result plus snapshot. The identical failure
|
||||
reproduces at the tree-identical #182 head `7a3a55d`; 2B-1 changes no
|
||||
initial-target behavior. Correct this as a Journey/GPU-initial-target
|
||||
ratchet side quest on `main`, then integrate it here and rerun that
|
||||
touched gate before opening the 2B-1 PR.
|
||||
- **The former deterministic red is resolved on `main`.** PR #183
|
||||
corrected `gpu_initial_target_acceptance` through the public
|
||||
`pmacs --gpu .` path, consumed the asynchronous dired snapshot, and
|
||||
retained the managed daemon before the wait so failure cleanup remains
|
||||
effective. The code integration auto-composed; the full matrix still
|
||||
has to prove the combined tree.
|
||||
- **Next ordering is fixed:** 2B-2 branches from `main` only after 2B-1
|
||||
lands; 2B-3 branches only after 2B-2 lands. The daemon epoch machine
|
||||
belongs to 2B-2; the GPU band and negotiated capability flip belong
|
||||
|
|
@ -429,10 +422,12 @@ gate.**
|
|||
`docs/agent-handoff.md` §1; the two round lessons are in §5.
|
||||
- Landed-docs follow-up merged as **#156** (`main` @ `d152120`,
|
||||
2026-07-25).
|
||||
- **Stage 2 framing: `docs/bottom-panel-stage2-framing.md` revision 5**,
|
||||
on branch `githubsucks/bottom-panel-stage2-framing` (three commits,
|
||||
one per pre-implementation revision), worktree `../pmacs-bp-stage2`, based on
|
||||
`githubsucks/main` @ `ccf29e3`. Round 1 closed 2 blocking + 3 high;
|
||||
- **Stage 2 framing: `docs/bottom-panel-stage2-framing.md` revision 5**
|
||||
is commit `56301ed` on branch `githubsucks/bottom-panel-stage2b`,
|
||||
worktree `../pmacs-bp-stage2b`. Revisions 1–4 remain on
|
||||
`githubsucks/bottom-panel-stage2-framing` (head `4fbd47f`, four
|
||||
framing commits, revision 4 at `49757e5`). Round 1 closed 2 blocking +
|
||||
3 high;
|
||||
round 2 closed 1 blocking + 2 high + 1 medium and decided both open
|
||||
items; round 3 closed 1 blocking + 1 high + 1 medium. No open items
|
||||
remain. Revision 5 adds no decision; it records the approved
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
# Agent handoff — cross-machine continuity
|
||||
|
||||
**Last updated: 2026-07-27, after Journey Stage 1a (#182) — the
|
||||
directory-open seam and golden-journey ratchet, incorporating terminal
|
||||
configuration + copy mode (#180) — and after terminal copy mode (#178) — `C-c C-t`
|
||||
**Last updated: 2026-07-28, after the Journey/GPU directory-target
|
||||
ratchet (#183), following Journey Stage 1a (#182), which made directory
|
||||
startup one coherent local/daemon/GPU path and incorporated the terminal
|
||||
configuration + copy mode landed-doc work (#180); following terminal
|
||||
copy mode (#178) — `C-c C-t`
|
||||
materializes a terminal's whole retained range into an ordinary buffer,
|
||||
plus `Buffer::set_generated_contents`, the first genuinely immutable
|
||||
generated-buffer write path — and its landed-doc pair (#168); following
|
||||
|
|
@ -41,12 +43,13 @@ reads it the way you just did.
|
|||
For volatile branches, checkpoints, verification, and recovery
|
||||
commands, read `docs/active-work.md` immediately after this file.
|
||||
|
||||
## 1. Where the project stands (2026-07-27)
|
||||
## 1. Where the project stands (2026-07-28)
|
||||
|
||||
- `main` @ `c2d56ff` (Journey Stage 1a #182, incorporating terminal
|
||||
configuration + copy mode #180, atop Lean 4 Stage 4b #181, the dired
|
||||
Stage 1 landed docs #169 and the PTY-terminate diagnostic #176,
|
||||
terminal copy mode #178, the GPU-terminal-input landed docs #168, Lean 4 Stage 4a
|
||||
- `main` @ `7fd646d` (Journey/GPU directory-target ratchet #183, atop
|
||||
Journey Stage 1a #182, incorporating terminal configuration + copy
|
||||
mode landed docs #180, Lean 4 Stage 4b #181, the dired Stage 1 landed
|
||||
docs #169 and the PTY-terminate diagnostic #176, terminal copy mode
|
||||
#178, the GPU-terminal-input landed docs #168, Lean 4 Stage 4a
|
||||
#179, bottom-panel Stage 2A
|
||||
#177, the bottom-panel Stage 2 framing #175, terminal configuration
|
||||
Stage 1 #173, Lean 4 Stage 3b #170, Stage 3a #167, the CRDT undo repro
|
||||
|
|
|
|||
|
|
@ -936,10 +936,20 @@ fn run_headless_managed_probe(
|
|||
}
|
||||
};
|
||||
let mut client = managed.client;
|
||||
let initial_message = client.take_initial_message();
|
||||
let initial_target_ready = matches!(
|
||||
client.take_initial_message(),
|
||||
initial_message.as_ref(),
|
||||
Some(InstanceMessage::BufferSnapshot { .. })
|
||||
);
|
||||
let mut buffer_facts = ManagedProbeBufferFacts::default();
|
||||
if let Some(message) = initial_message.as_ref()
|
||||
&& let Err(error) = buffer_facts.observe(message)
|
||||
{
|
||||
let contents = format!("phase=error\nerror={error}\n");
|
||||
let _ = write_probe_report(report, &contents);
|
||||
eprintln!("pmacs-gpu managed probe: {error}");
|
||||
return 7;
|
||||
}
|
||||
let daemon = managed.daemon;
|
||||
let protocol = client.server_protocol_version();
|
||||
|
||||
|
|
@ -961,8 +971,14 @@ fn run_headless_managed_probe(
|
|||
let mut last_wait_result = None;
|
||||
let mut last_disconnect = String::new();
|
||||
if ready
|
||||
&& let Err(error) =
|
||||
write_managed_probe_report(report, "ready", protocol, &daemon, &disconnect)
|
||||
&& let Err(error) = write_managed_probe_report(
|
||||
report,
|
||||
"ready",
|
||||
protocol,
|
||||
&daemon,
|
||||
&buffer_facts,
|
||||
&disconnect,
|
||||
)
|
||||
{
|
||||
eprintln!(
|
||||
"pmacs-gpu managed probe: writing {} failed: {error}",
|
||||
|
|
@ -976,11 +992,23 @@ fn run_headless_managed_probe(
|
|||
}
|
||||
match event_rx.recv_timeout(Duration::from_millis(50)) {
|
||||
Ok(AttachEvent::Message(message)) => {
|
||||
if matches!(*message, InstanceMessage::BufferSnapshot { .. }) && !ready {
|
||||
let is_snapshot = matches!(*message, InstanceMessage::BufferSnapshot { .. });
|
||||
if let Err(error) = buffer_facts.observe(&message) {
|
||||
let contents = format!("phase=error\nerror={error}\n");
|
||||
let _ = write_probe_report(report, &contents);
|
||||
eprintln!("pmacs-gpu managed probe: {error}");
|
||||
return 7;
|
||||
}
|
||||
if is_snapshot {
|
||||
ready = true;
|
||||
if let Err(error) =
|
||||
write_managed_probe_report(report, "ready", protocol, &daemon, &disconnect)
|
||||
{
|
||||
if let Err(error) = write_managed_probe_report(
|
||||
report,
|
||||
"ready",
|
||||
protocol,
|
||||
&daemon,
|
||||
&buffer_facts,
|
||||
&disconnect,
|
||||
) {
|
||||
eprintln!(
|
||||
"pmacs-gpu managed probe: writing {} failed: {error}",
|
||||
report.display()
|
||||
|
|
@ -1006,9 +1034,14 @@ fn run_headless_managed_probe(
|
|||
|| wait_result != last_wait_result
|
||||
|| disconnect != last_disconnect)
|
||||
{
|
||||
if let Err(error) =
|
||||
write_managed_probe_report(report, "ready", protocol, &daemon, &disconnect)
|
||||
{
|
||||
if let Err(error) = write_managed_probe_report(
|
||||
report,
|
||||
"ready",
|
||||
protocol,
|
||||
&daemon,
|
||||
&buffer_facts,
|
||||
&disconnect,
|
||||
) {
|
||||
eprintln!(
|
||||
"pmacs-gpu managed probe: writing {} failed: {error}",
|
||||
report.display()
|
||||
|
|
@ -1021,9 +1054,14 @@ fn run_headless_managed_probe(
|
|||
}
|
||||
|
||||
if ready && stdin_closed {
|
||||
if let Err(error) =
|
||||
write_managed_probe_report(report, "complete", protocol, &daemon, &disconnect)
|
||||
{
|
||||
if let Err(error) = write_managed_probe_report(
|
||||
report,
|
||||
"complete",
|
||||
protocol,
|
||||
&daemon,
|
||||
&buffer_facts,
|
||||
&disconnect,
|
||||
) {
|
||||
eprintln!(
|
||||
"pmacs-gpu managed probe: writing {} failed: {error}",
|
||||
report.display()
|
||||
|
|
@ -1043,11 +1081,42 @@ fn run_headless_managed_probe(
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct ManagedProbeBufferFacts {
|
||||
snapshots: u32,
|
||||
last_snapshot_text: String,
|
||||
}
|
||||
|
||||
impl ManagedProbeBufferFacts {
|
||||
fn observe(&mut self, message: &InstanceMessage) -> Result<(), String> {
|
||||
let InstanceMessage::BufferSnapshot { crdt_snapshot, .. } = message else {
|
||||
return Ok(());
|
||||
};
|
||||
let doc = loro::LoroDoc::new();
|
||||
doc.import(crdt_snapshot)
|
||||
.map_err(|error| format!("BufferSnapshot import failed: {error:?}"))?;
|
||||
self.snapshots += 1;
|
||||
self.last_snapshot_text = doc.get_text(LORO_TEXT_CONTAINER).to_string();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn hex_bytes(bytes: &[u8]) -> String {
|
||||
use std::fmt::Write as _;
|
||||
|
||||
let mut encoded = String::with_capacity(bytes.len() * 2);
|
||||
for byte in bytes {
|
||||
let _ = write!(encoded, "{byte:02x}");
|
||||
}
|
||||
encoded
|
||||
}
|
||||
|
||||
fn write_managed_probe_report(
|
||||
report: &Path,
|
||||
phase: &str,
|
||||
protocol: u32,
|
||||
daemon: &attach::ManagedDaemonFacts,
|
||||
buffer_facts: &ManagedProbeBufferFacts,
|
||||
disconnect: &str,
|
||||
) -> std::io::Result<()> {
|
||||
use std::fmt::Write as _;
|
||||
|
|
@ -1056,6 +1125,12 @@ fn write_managed_probe_report(
|
|||
let _ = writeln!(out, "phase={phase}");
|
||||
let _ = writeln!(out, "server_protocol_version={protocol}");
|
||||
let _ = writeln!(out, "buffer_snapshot=true");
|
||||
let _ = writeln!(out, "buffer_snapshots={}", buffer_facts.snapshots);
|
||||
let _ = writeln!(
|
||||
out,
|
||||
"last_snapshot_hex={}",
|
||||
hex_bytes(buffer_facts.last_snapshot_text.as_bytes())
|
||||
);
|
||||
let _ = writeln!(out, "spawned_daemon={}", daemon.spawned_daemon());
|
||||
let _ = writeln!(
|
||||
out,
|
||||
|
|
|
|||
|
|
@ -121,6 +121,19 @@ mod crdt {
|
|||
.collect()
|
||||
}
|
||||
|
||||
fn decode_hex(encoded: &str) -> String {
|
||||
assert_eq!(encoded.len() % 2, 0, "hex payload must have even length");
|
||||
let bytes = encoded
|
||||
.as_bytes()
|
||||
.chunks_exact(2)
|
||||
.map(|pair| {
|
||||
let pair = std::str::from_utf8(pair).expect("hex pair is UTF-8");
|
||||
u8::from_str_radix(pair, 16).expect("decode hex pair")
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
String::from_utf8(bytes).expect("snapshot text is UTF-8")
|
||||
}
|
||||
|
||||
fn wait_for_fact(
|
||||
report: &Path,
|
||||
key: &str,
|
||||
|
|
@ -356,6 +369,16 @@ mod crdt {
|
|||
}
|
||||
|
||||
impl ManagedProbe {
|
||||
fn from_child(mut child: Child, report: &Path) -> Self {
|
||||
let stdin = child.stdin.take().expect("probe stdin");
|
||||
Self {
|
||||
child,
|
||||
stdin: Some(stdin),
|
||||
report: report.to_owned(),
|
||||
daemon_pid: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn(socket: &Path, report: &Path, daemon_executable: &Path, home: &Path) -> Self {
|
||||
Self::spawn_with_env(socket, report, daemon_executable, home, &[])
|
||||
}
|
||||
|
|
@ -418,18 +441,11 @@ mod crdt {
|
|||
for (key, value) in envs {
|
||||
command.env(key, value);
|
||||
}
|
||||
let mut child = command.spawn().expect("spawn managed probe");
|
||||
let stdin = child.stdin.take().expect("probe stdin");
|
||||
Self {
|
||||
child,
|
||||
stdin: Some(stdin),
|
||||
report: report.to_owned(),
|
||||
daemon_pid: None,
|
||||
}
|
||||
Self::from_child(command.spawn().expect("spawn managed probe"), report)
|
||||
}
|
||||
|
||||
fn wait_ready(&mut self) -> HashMap<String, String> {
|
||||
let facts = wait_for_fact(&self.report, "phase", "ready", Duration::from_secs(10));
|
||||
fn wait_for(&mut self, key: &str, expected: &str) -> HashMap<String, String> {
|
||||
let facts = wait_for_fact(&self.report, key, expected, Duration::from_secs(10));
|
||||
if facts
|
||||
.get("spawned_daemon")
|
||||
.is_some_and(|value| value == "true")
|
||||
|
|
@ -439,6 +455,10 @@ mod crdt {
|
|||
facts
|
||||
}
|
||||
|
||||
fn wait_ready(&mut self) -> HashMap<String, String> {
|
||||
self.wait_for("phase", "ready")
|
||||
}
|
||||
|
||||
fn close(mut self) -> std::process::ExitStatus {
|
||||
self.stdin.take();
|
||||
wait_for_fact(&self.report, "phase", "complete", Duration::from_secs(5));
|
||||
|
|
@ -662,6 +682,75 @@ mod crdt {
|
|||
assert!(probe.close().success());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public_gpu_directory_target_reaches_dired_and_leaves_the_daemon_usable() {
|
||||
let temp = secure_tempdir();
|
||||
let socket = temp.path().join("directory-target.sock");
|
||||
let report = temp.path().join("directory-target-report");
|
||||
let wrapper = temp.path().join("headless-gpu");
|
||||
let listed_name = "listed-before-bootstrap.txt";
|
||||
fs::write(temp.path().join(listed_name), "listed\n").expect("write listed file");
|
||||
write_script(
|
||||
&wrapper,
|
||||
"test \"$1\" = \"--managed-attach\"\n\
|
||||
socket=$2\n\
|
||||
daemon=$3\n\
|
||||
shift 3\n\
|
||||
exec \"$PMACS_REAL_GPU\" --headless-managed-probe \
|
||||
\"$socket\" \"$PMACS_TEST_REPORT\" \"$daemon\" \"$@\"",
|
||||
);
|
||||
|
||||
let mut command = Command::new(pmacs_binary());
|
||||
command
|
||||
.args(["--gpu", "--socket"])
|
||||
.arg(&socket)
|
||||
.arg(".")
|
||||
.current_dir(temp.path())
|
||||
.env(TEST_GPU_OVERRIDE, &wrapper)
|
||||
.env("PMACS_REAL_GPU", gpu_binary())
|
||||
.env("PMACS_TEST_REPORT", &report)
|
||||
.env("HOME", temp.path())
|
||||
.env("XDG_CONFIG_HOME", temp.path())
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null());
|
||||
let mut directory =
|
||||
ManagedProbe::from_child(command.spawn().expect("spawn public GPU command"), &report);
|
||||
|
||||
// The public root broker and real managed GPU connector must stay
|
||||
// alive through Journey N2's asynchronous dired commit. Snapshot
|
||||
// one is the deliberately pre-existing bootstrap document; snapshot
|
||||
// two is the post-quiescence directory surface.
|
||||
// Capture the spawned daemon's PID from the ready report first so
|
||||
// ManagedProbe::drop can terminate it if snapshot two never arrives.
|
||||
let ready = directory.wait_ready();
|
||||
assert_eq!(
|
||||
ready.get("spawned_daemon").map(String::as_str),
|
||||
Some("true")
|
||||
);
|
||||
let facts = directory.wait_for("buffer_snapshots", "2");
|
||||
let listing = decode_hex(&facts["last_snapshot_hex"]);
|
||||
let canonical = fs::canonicalize(temp.path()).expect("canonical directory");
|
||||
let mut lines = listing.lines();
|
||||
let expected_header = format!("{}:", canonical.display());
|
||||
assert_eq!(
|
||||
lines.next(),
|
||||
Some(expected_header.as_str()),
|
||||
"the replacement snapshot must be dired's directory surface:\n{listing}"
|
||||
);
|
||||
assert!(
|
||||
lines.any(|line| line.trim_end().ends_with(listed_name)),
|
||||
"dired must list the file that existed before bootstrap:\n{listing}"
|
||||
);
|
||||
|
||||
fs::write(temp.path().join("still-alive.txt"), "alive\n").expect("write survivor");
|
||||
let survivor = attach_target(&socket, temp.path(), Path::new("still-alive.txt"));
|
||||
assert_eq!(survivor.replica.materialize_string(), "alive\n");
|
||||
|
||||
drop(survivor);
|
||||
assert!(directory.close().success());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn malformed_or_unloadable_targets_fail_closed_without_poisoning_the_daemon() {
|
||||
let temp = secure_tempdir();
|
||||
|
|
@ -673,7 +762,6 @@ mod crdt {
|
|||
(cwd.clone(), Vec::new()),
|
||||
(cwd.clone(), b"bad\0name".to_vec()),
|
||||
(cwd.clone(), vec![b'x'; 32 * 1024 + 1]),
|
||||
(cwd.clone(), b".".to_vec()),
|
||||
];
|
||||
for (index, (bad_cwd, bad_path)) in invalid.into_iter().enumerate() {
|
||||
let (frontend_id, mut stream, messages) = open_raw_target(&socket, bad_cwd, bad_path);
|
||||
|
|
|
|||
Loading…
Reference in New Issue