agent-desktop/.github/workflows
Lahfir 18daaa8215
feat: windows toolchain, ci & com bootstrap (sub-phase 2.1) (#112)
* docs: correct the windows sub-phase facts research disproved

Sixteen replacements, no annotations. The document is the product's source of
truth and now reads true.

The COM bootstrap is split by consumer. CoInitializeEx is right for the CLI and
wrong for the cdylib, which fails RPC_E_CHANGED_MODE against any host thread
already in an STA and whose per-thread balance can never be released from a Drop
on another thread; the library path uses CoIncrementMTAUsage, whose cookie is
thread-agnostic. MTA is recorded as a requirement rather than a preference,
because the single-threaded apartment can prevent handler removal outright.

Two things the sub-phase asserted were not reachable. Its private-file seam named
a boundary that does not exist: every write site is in core with no adapter
handle, and core may not depend on the platform crate. The document now states
that constraint and leaves the mechanism to the plan. Its CI paragraph claimed
all runners enforce clippy, isolation, and the size cap; only the macOS job does,
and the other two lanes each run a single lib invocation.

The event invariant said handlers are drained on the dedicated MTA thread. They
are not: delivery arrives on several UIA-owned threads at once, the registering
worker among them and the main thread never, so handler state has to be safe for
concurrent delivery.

Runner registration gains the hardening a public repository requires, and gains
the measurement it already owed the ledger: it closes the deferred RDP row rather
than merely documenting session isolation. The DPI bootstrap records that
awareness cannot be verified by read-back, since the query API has no enumerant
for the value being set.

The ledger's hunk index moves with them, 37 to 40. Hunks backed by research
rather than by a probe are labelled as such, so an external citation cannot pass
itself off as a measurement.

* docs: move the windows runner registration to the sub-phase that needs it

Sub-phase 2.1 was to register a self-hosted interactive Windows runner that
nothing in 2.1 through 2.11 uses. On a public repository that is a persistently
registered runner sitting idle behind ten sub-phases, and GitHub's own guidance
is that self-hosted runners should almost never be used for public repositories
at all. Registration moves to 2.12, the first sub-phase whose gate needs a real
desktop, and it takes the hardening requirements with it: dispatch-only
triggering, a written fork-PR approval policy, and an explicit
ephemeral-versus-persistent decision.

The deferred RDP row moves with it. Its closure was never documentation -- it is
a measurement that only exists once a second, non-console session does, so 2.12
now owns closing it rather than 2.1 owning a promise it could not keep.

Two probe scripts hardcoded the old closure sub-phase in text they emit. The
scripts were corrected and their probes re-run, so the captures were regenerated
rather than edited by hand.

* docs: plan the windows toolchain, ci, and com bootstrap sub-phase

Eight units covering the CI lane extension, Win32 bindings in the platform
crate, the COM and DPI bootstrap, the adapter session, the permission probe,
and a private-file layer rebuilt from scratch against sub-phase 2.0's measured
evidence.

Three reviewers found what the first draft got wrong. It installed both the
apartment and the private-file seam from ad_init, which the FFI header labels
optional and whose own doc promises it initialises nothing -- so any host that
skipped it would have run every private write through the unhardened default
with nothing to distinguish the two states. The install moves to the site the
macOS arm already uses, where a consumer cannot skip it.

It also planned a layer that validates security state it never authors. The
deleted implementation at least built an owner-only protected descriptor at
creation; the rebuild now does too, and rejects reparse points per component
the way the unix path rejects symlinks.

The ACE validation order was still wrong in the same shape as the defect it
exists to prevent: checking the type before forming the SID pointer still
dereferences the header at an offset nothing has bounds-checked. The order is
now extent-first, with the ACE count treated as advisory rather than trusted.

The seam routes at core's five private-file primitives rather than at a list of
call sites, which covers the lock and liveness paths a list had missed and
needs no call-site edits at all.

* docs: cut acl validation from the windows private-file design

Reverses the ancestor-walk decision on measurement. A plain leaf under the user
profile already inherits SYSTEM, Administrators and the user with no Users
entry, all inherited and none explicit, so the walk would verify a property
Windows already guarantees. Unix has to author its permission because the
default there is world-readable; Windows does not, because the default is
already private.

The only principal the walk would additionally exclude is another
administrator, who holds SeTakeOwnershipPrivilege and can seize any object
whatever its DACL. Building a chain-walking validator against someone who can
bypass it costs more than it buys, and the cost is specific: parsing ACEs is
what the previous layer did, and its AceSize handling is the defect that took
the layer down. Removing the parser removes the bug class rather than fixing
it, so no successor module exists and a test asserts the ACE symbols never come
back.

What survives is the control with real unix parity and a real attack behind it:
per-component reparse-point rejection. That is an integrity control, not a
confidentiality one -- a junction planted on the path redirects where the
product writes, which no permission on the intended destination prevents.

Cross-admin isolation is now answered rather than open: out of scope, for the
same reason macOS never raised the question.

* test: resolve the test binary per platform via CARGO_BIN_EXE

* feat: add target-gated windows-sys bindings to the windows crate

* feat: route private-file primitives through a core-defined platform seam

* feat: establish com apartment and dpi awareness at unskippable call sites

* feat: report windows permissions truthfully from a host-independent seam

* ci: extend the windows lane to the full package surface

* feat: give the windows adapter a session that owns apartment lifetime

* feat: rebuild windows private-file hardening behind the core seam

* test: pin the inherited-acl assumption module-free on a real profile leaf

* feat: install the windows private-file seam at both unskippable consumers

* fix: scope the temp lease per write and fold duplicated windows helpers

* fix: route windows session and trace directory creation through the private-file seam

* fix: scope the com bootstrap to adapter commands and harden review-flagged tests

* test: cover the owner, locality, replace-acl, and sweep-race branches of the windows hardening

* fix: pin the private-file directory chain to close the junction-swap window

* docs: settle the user-output seam exclusion and pin it with a test

* refactor: split the trace file plumbing and ffi adapter tests into sibling modules
2026-07-27 18:35:51 -07:00
..
ci.yml feat: windows toolchain, ci & com bootstrap (sub-phase 2.1) (#112) 2026-07-27 18:35:51 -07:00
codeql.yml chore(deps): bump the github-actions group across 1 directory with 3 updates (#65) 2026-06-24 15:43:37 -07:00
native-e2e.yml feat!: implement Playwright-grade foundation contract 2026-07-20 00:21:38 -07:00
release.yml feat!: implement Playwright-grade foundation contract 2026-07-20 00:21:38 -07:00
supply-chain.yml chore: track planning artifacts with secret and privacy scanning (#109) 2026-07-26 01:41:55 -07:00