--- id: ACTIVITY-WP-0032 type: workplan title: "Adopt the Glas profile-driven execution contract" domain: infotech repo: activity-core status: proposed owner: claude topic_slug: activity-core priority: medium created: "2026-08-21" updated: "2026-08-21" related: - ACTIVITY-WP-0026 - ACTIVITY-WP-0029 - ACTIVITY-WP-0031 - ACT-ADR-005 - GLAS-WP-0004 - REIN-A-0002 --- # Adopt the Glas profile-driven execution contract ## Origin `glas-harness` reported (2026-08-20) that GLAS-WP-0004 contract 1.0 is implemented and proven: the same bounded task completed through **two** different reins (`rein-aharness`, `rein-openweights`) via an explicit `harness_profile_ref`, with real commits and verified sandbox destruction. Their guidance to us: activity-core stays scheduler and task source, and should name an **approved profile**, never a concrete rein. ACTIVITY-WP-0026 is `finished`, so this does not belong as an amendment to it. ## Why this is worth doing Today an ops_run carries `approach_hint` (`orm.py:172`), a free-text string the executor interprets at claim time. That binding is too late and too weak. Live example from 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'] def='2969816c-…' ``` The run was claimed, then died because nothing could execute it. A `harness_profile_ref` is resolved and **refused before sandbox creation**, so an unroutable request fails at emission with a clear reason instead of consuming a claim and a lease. Second motivation: we are currently coupled to one backend in practice — every claim in the last week is `rein-aharness@railiance01`. The dual-rein proof means that coupling is now avoidable rather than inherent. ## Boundary (must not drift) activity-core stays **when / what / where**. Adopting this contract must not turn it into an executor — SCOPE drift risk #1 ("convenience execution"). We emit an authorized, profile-named request and record normalized evidence. We do not select reins, provision sandboxes, acquire credentials, or run the inner agentic loop. Glas owns profile resolution and the outer loop; the rein owns its own inner loop; `sand-boxer` owns isolation. We also do not author the attribution refs the contract accepts (`assignment_ref`, `role_ref`, `duty_ref`, `goal_refs`, `resource_envelope_refs`). Those come from workforce/leadership vocabulary in `info-tech-canon`; activity-core carries them through, and must not invent org roles here (ACTIVITY-WP-0029 responsibility map). ## Open questions (asked of glas-harness 2026-08-21, not yet answered) 1. Is `approach_hint` expected to be **replaced** by `harness_profile_ref`, or to coexist? 2. Is the profile registry authoritative in glas-harness, so we validate refs by calling them rather than mirroring a catalogue that can drift? T01 is blocked on these; the rest follows the answer. ## Decide the invocation shape ```task id: ACTIVITY-WP-0032-T01 status: wait priority: high ``` Our claim path is **pull-based**: we insert `ops_run(open)` and rein-aharness claims it. The Glas contract is shaped as a **call**. Those do not compose automatically, and the choice is architectural, not mechanical. Record a decision (ACT-ADR) between: - **A — queue carries the profile.** `ops_run` grows `harness_profile_ref` plus the attribution refs; a Glas-aware claimer resolves the profile. Keeps lease semantics, durability, and the operator console unchanged. Keeps one more indirection between authorization and execution. - **B — activity-core calls the Glas gateway.** Direct invocation with the approved profile. Matches the contract as designed and fails unroutable requests before sandbox creation. Costs us the claim/lease/retry machinery ACTIVITY-WP-0026 just built, and moves activity-core closer to the executor boundary we deliberately hold. Do not start T02+ before this is resolved. Prefer A unless glas-harness states the profile must be resolved at emission time to keep its refusal guarantee. ## Carry the profile and attribution refs ```task id: ACTIVITY-WP-0032-T02 status: wait priority: medium ``` Depends on T01. Add `harness_profile_ref` and the correlation/attribution refs to the ops_run model, emission path (`activities.py`), queue projection (`ops_run_queue.py`), and run artefacts (`run_artifacts.py`), with a migration. Definitions declare the profile; rules pass it through. Decide `approach_hint`'s fate per open question 1 — if it coexists, document which wins; if it is replaced, strangle it rather than leaving two routing fields. ## Validate refs at emission ```task id: ACTIVITY-WP-0032-T03 status: wait priority: medium ``` Depends on T01/T02. An unknown or incompatible profile must be refused when the ActivityDefinition is synced or the run is emitted — not discovered at claim time, which is the failure mode this workplan exists to remove. Per open question 2, prefer calling the glas-harness registry over mirroring it. Include the offline/unreachable behaviour: a registry we cannot reach must not silently downgrade to "emit anything". ## Record normalized execution evidence ```task id: ACTIVITY-WP-0032-T04 status: wait priority: low ``` Depends on T02. Glas emits normalized, non-secret execution evidence. Land it in `ops_runs.result` and the evidence sink so the production status surface (ACTIVITY-WP-0031-T03) shows what actually executed, through which profile and rein. Keep the field allowlist discipline from ACTIVITY-WP-0031: no provider blobs, no credential material in run artefacts. ## Prove on one definition ```task id: ACTIVITY-WP-0032-T05 status: wait priority: low ``` Depends on T03/T04. Convert exactly one low-risk definition, run it on railiance01, and capture evidence. Do not convert the fleet before one definition is proven end to end. Note that FI and Binky are unsuitable as the pilot while ACTIVITY-WP-0031-T01 is unresolved — their failures are currently provider-credential failures and would mask the result. ## Acceptance - [ ] Invocation shape decided and recorded as an ADR, with the boundary stated - [ ] ops_run carries an approved `harness_profile_ref`; no definition names a concrete rein - [ ] Unroutable profiles are refused at emission, not at claim - [ ] `approach_hint` is either strangled or documented as subordinate - [ ] Normalized Glas evidence is visible in production status - [ ] One definition proven on railiance01 end to end