--- id: ACT-ADR-006 type: architecture-decision-record title: "Profile-driven execution selection over the ops_run pull queue" status: accepted owner: activity-core revision: "accepted-1" last_reviewed: "2026-08-21" review_interval: 6m decided_by: Bernd Worsch date: "2026-08-21" scope: cross-repo affects: - activity-core - glas-harness - rein-aharness - sand-boxer tags: - architecture - activity-core - ops-run - glas-harness - execution supersedes_notes: > Extends ACT-ADR-005. Does not change the ops_run claim/lease plane; it changes what a queued run says about how it should execute. --- # ACT-ADR-006: Profile-driven execution selection over the ops_run pull queue ## Status **Accepted** (2026-08-21). Hub decision `147beec6-7fe7-4837-8e3a-4264a240379d` ("Glas contract 1.0 makes execution constellation selection explicit"). ## Context ACT-ADR-005 gave internal scheduled automation a claimable `ops_run` plane. What an ops_run said about *how* to execute was `approach_hint` — free text, interpreted by the claiming executor at claim time. That binding is too late. Live failure, railiance01 2026-08-17: ``` state=failed attempt=1 claim_owner=rein-aharness@railiance01 title=Run SBOM rescan for binect-js failure=no approach matched labels/definition; labels=['sbom','security','automated'] ``` The run was claimed and leased, *then* died because nothing could execute it. A claim was consumed to discover the request was unroutable. `glas-harness` GLAS-WP-0004 closed contract 1.0 and proved it by running one bounded task through two different reins (`rein-aharness`, `rein-openweights`) via an explicit `harness_profile_ref`, with real commits and verified sandbox destruction. Meanwhile every activity-core claim in the week to 2026-08-21 was `rein-aharness@railiance01` — a coupling that is now avoidable rather than inherent. Two shapes were considered: keep the pull queue and carry the profile as payload, or have activity-core call the Glas gateway directly. glas-harness, which owns the contract, advised the first for the initial migration. ## Decision ### 1. The queue stays; the payload changes activity-core continues to insert `ops_run(open)` and executors continue to claim under lease. Scheduling topology does not change. What changes is that a queued run carries an exact **versioned `harness_profile_ref`** plus the correlation / assignment / role / duty / goal / resource-envelope references. The claiming executor passes that request into Glas, which resolves or refuses it **before sandbox creation**. This changes the execution contract without simultaneously changing the scheduling topology — one variable at a time. A direct activity-core → Glas call may be evaluated later, independently, if pull delivery stops meeting operational needs. ### 2. `harness_profile_ref` is authoritative; `approach_hint` is legacy The two coexist with **distinct semantics**, not as fallbacks for one another: - `harness_profile_ref` is the authoritative execution-constellation selector. - `approach_hint` remains only a legacy activity/definition-matching hint while producers and consumers migrate. `approach_hint` must **not** override, synthesize, or fall back from an absent or invalid `harness_profile_ref` on governed execution. A missing profile is an error, never an invitation to guess from a hint. Once inventory shows no caller depends on `approach_hint` for runtime selection, that use is deprecated. ### 3. activity-core does not mirror the profile catalog The catalog in glas-harness is authoritative. activity-core must not maintain an independent list, which would drift and produce a second, disagreeing opinion about what is executable. Consequence, accepted knowingly: Glas exposes deterministic validation through its package and CLI but **no network validation service** today, so activity-core cannot remotely validate a profile ref at emission time. We therefore validate only what is local and structural — that a ref is present and well-formed for governed execution — and rely on the **execution-side Glas resolver as the mandatory fail-closed check**. This is weaker than refusing at emission, and deliberately so: an unreachable or absent validation service must never downgrade into "emit anything". If emit-time remote validation is later required, it needs a separately scoped Glas API and its own decision — not a mirrored catalogue here. Even unvalidated at emit, this is strictly better than the 2026-08-17 failure: refusal happens deterministically before sandbox creation rather than after a claim and lease were consumed. ### 4. activity-core does not become an executor We emit an authorized, profile-named request and record normalized evidence. We do not select reins, provision sandboxes, acquire credentials, or run any inner agentic loop. Glas owns profile resolution and the outer loop; the rein owns its inner loop; `sand-boxer` owns isolation. This is SCOPE drift risk #1 ("convenience execution") and this ADR does not relax it. The attribution references we carry (`assignment_ref`, `role_ref`, `duty_ref`, `goal_refs`, `resource_envelope_refs`) are **passed through**, not authored here. Their vocabulary belongs to `info-tech-canon`; activity-core must not invent org roles (ACTIVITY-WP-0029 responsibility map). ACT-ADR-007's bounded-operation exception does not alter this decision. A code-registered fixed maintenance operation is not a Glas/rein execution constellation, and the exception cannot be used to run an agent loop or execute an `ops_run` inside activity-core. ## Consequences - `ops_runs` grows `harness_profile_ref` and the attribution refs; the emission path, queue projection, and run artefacts carry them. - Definitions declare a profile; rules pass it through. - Unroutable requests fail before sandbox creation instead of at claim time. - activity-core is no longer coupled to one rein in practice. - Emit-time validation remains a known gap, owned by a future Glas API. - Implementation: **ACTIVITY-WP-0032**.