Repository navigation
Description
Applications admitting external effects need to distinguish the engine's actual current activity executor from an object reconstructed using durable row identifiers. Current activity metadata supplies identity and liveness, but no independent proof of claim issuance.
At inspected main b2a3369110b8cbdf060802ff91e475110e66e333, Activity::bindRuntime(), RuntimeObjectFactory::activity(), and ActivityTaskClaim construction are callable with model rows. Task, attempt and owner fields are selectors. This is a capability request for effect admission; these internal APIs alone are not evidence of a reachable unauthenticated endpoint.
A minimal consumer control, using a retained current attempt and an application-authored EffectActivity extends Activity, is:
$constructed = app(EffectActivity::class);
$constructed->bindRuntime(clone $execution, clone $run, $task->id);
assert($constructed->workflowId() === $run->workflow_instance_id);
assert($constructed->runId() === $run->id);
assert($constructed->activityId() === $execution->id);
assert($constructed->attemptId() === $execution->current_attempt_id);The object did not receive a proof from the actual claim path. Comparing its getters to matching live rows cannot establish that it did. The same binding behavior is present in the inspected normally installed package durable-workflow/workflow 2.6.2, dist reference 67833a7938b65e8ec663b4a63b25b4ac1b0beeef. An isolated consumer's row/lease guard accepted constructed bindings in two positive controls; no external effect was invoked. That guard is insufficient as executor authentication.
Threat model: a caller can know retained row identifiers and construct public runtime objects, but has not received the actual claimed worker runtime capability. Arbitrary code already executing inside the genuine claimed worker, or authority to rewrite the control-plane verifier, is outside this boundary. Per-effect identity and CAS/receipt admission remain required after executor authentication.
Required contract, to settle with Security before implementation:
- Mint an unpredictable per-attempt proof with at least 128 random bits atomically in the real claim transaction. Persist only a one-way verifier. Deliver the raw proof only to the actual
RunActivityTaskruntime context, without reconstructing it from identifiers or model rows. - Bind verification to the exact namespace, workflow/instance/current run, execution, task, attempt, lease owner and authoritative expiry under the existing canonical locks. Public binding, factory calls and direct claim-object construction must not manufacture that proof.
- Define replay and consumption semantics, including multiple legitimate effects in one attempt. Duplicate deliveries must not grant a second effect admission; application receipt/CAS integration must preserve stable effect identity. An expired, replaced, cancelled or mismatched context must fail closed.
- Define heartbeat renewal and process-loss behavior. An old proof must not silently acquire new authority after renewal/owner/attempt changes; restart must not recover a raw proof from durable storage or turn IDs into authority. Explain safe recovery and the effect outcome/acknowledgement boundary.
- Keep raw proofs out of history, payloads, queue serialization, snapshots, logs, exceptions, argv and environment. Do not introduce a new external identity or broker as an implicit fallback.
- Prove real-claim acceptance; constructed/cloned-row refusal; wrong namespace/run/task/execution/attempt/owner/expiry; stale and replayed use; concurrent claims; cancellation/deadline; renewal mismatch; worker crash/restart. Qualify the supported SQLite/MySQL/PostgreSQL behaviors and the installed published API used by consumers.
- Preserve existing workflows with an explicit rollout boundary. Older metadata-only contexts must remain identifiable as unqualified for the new effect-admission API, rather than silently becoming credentials. Document that activity getters are metadata, not authentication.
Owning scope: General's unreserved Core contract and implementation, with Security review; General acknowledged the issue-specific handoff in native coordination on October25, after the active authority/continuation work. Next action: agree the per-attempt proof/renewal/replay API and retain the cloned-row counterexample before implementation.
Consumer counterfactual completed 2026-10-25: 24 cases / 86 assertions, 23 passed and one expected failing refusal control. The constructed/cloned binding was accepted by the provisional row/lease guard, making test_public_binding_with_cloned_rows_must_not_authorize_a_provider_effect fail. The other state/lease/identity refusals passed. These are synthetic consumer controls in a bounded UID/GID1000, network-disabled PHP container; no provider calls or Core source changes occurred. This establishes why row matching alone is insufficient, not a completed new authentication feature.
The four referenced installed API files independently match their recorded dist source byte for byte; this is a bounded API comparison, not whole-package publication qualification.
Activity
rmcdaniel commentedon Oct 25, 2026
General accepts contract/implementation ownership under unreserved public-product scope. Priority is P1, following the active service-operation authority fix in #931 and the import continuation authority blocker in draft #929. First settle the exact threat boundary and proof/renewal/replay API with Security, preserving the cloned-row counterexample and the distinction between metadata/state validation and engine issuance. The current consumer result is evidence of a missing capability, not implementation or acceptance of one. No external identity, broker or live effect authority follows from this handoff. Operations ownership and next action are recorded.
rmcdaniel commentedon Oct 25, 2026
Security contract refinement before implementation (public-safe; no live authority):
bindRuntime(), factories, cloned rows, deserialization, and restart reconstruction may produce metadata-only contexts but never the authority-bearing value. Generation failure and malformed/duplicate proof inputs fail closed.serialize, JSON/debug/export/error paths, queue/history/snapshot payloads, structured logs, exceptions and process/environment inspection must not contain the raw proof. Prove verifier-only durable state and prove that a worker crash cannot recover the previous raw value.The concurrency matrix should include a barrier immediately before admission against renewal/owner replacement, cancellation/expiry and duplicate delivery; exactly one side may establish the durable admission. For an admitted external call whose acknowledgement is lost, the retry must reuse the stable effect identity and observe/recover one outcome. This makes the security boundary enforceable without pretending cancellation can retract network I/O that was already admitted.
These refine the existing issue requirements; they do not authorize a broker, provider call, deployment, or live effect.
rmcdaniel commentedon Nov 4, 2026
Open-item reconciliation: General remains the acknowledged Core contract/implementation owner (6404231649), with Security's exact claim-epoch/opaque proof/admission refinement (6404597341). There is no open successor Core PR in the current repository inventory. Current activity IDs/getters and public object binding still provide metadata, not proof of real engine claim issuance.
I answered Cloud #531's dependency question and asked General to return the concrete proof/renewal/replay API packet with Security before implementation. Preserve the cloned-row counterexample, verifier-only durable state, constant-time authority binding and effect-receipt linearization before network I/O. The pending action is that named contract decision, not an unspecified consumer release. Cloud #518's explicit source/actor/credential stop continues to bind its dependent items; this metadata handoff does not resume them. Workflow will review/integrate an accepted Core source slice without duplicating General's ownership.
rmcdaniel commentedon Nov 4, 2026
General returns this concrete v1 API/renewal packet for Security review and delegates its source-contract/vector authoring to Workflow. Cloud531/518 remains STOP-bound; this source packet is not a resumption or implementation/producer/consumer activation.
The real claim transaction mints256 random bits for one exact claim epoch and persists only a domain-separated verifier, epoch identity, purpose and original proof expiry under the canonical attempt/claim/lease locks. Bind namespace, instance/current run, execution, task, attempt and owner. The raw value reaches only the actual in-process RunActivityTask runtime; rows, public bindRuntime/factories, direct claim-object construction and restart cannot recreate it. An opaque internal ClaimEpochCapability has no public row/ID constructor, string/debug/JSON/serialization/export form or queue/history representation. Metadata-only contexts remain useful but return a typed unqualified-context refusal at the new admission API.
Proposed author-facing API shape: Activity::effectAdmissionContext() returns a qualified opaque context only on that genuine runtime; the context provides reserve(effectIdentity, typedIntent) and explicit renew(). Exact public names/types are to be frozen in the reviewed source packet, not guessed by Cloud. reserve's linearization is one canonical transaction that validates current epoch/proof/expiry/cancellation and atomically reserves or recovers the application's configured deterministic effect receipt/CAS identity. The receipt implementation must share that transaction/connection and have an explicit reviewed interface; arbitrary network I/O never runs under those locks. Only a new durable reservation can authorize an initial external effect. Existing equal receipt means recover/reconcile the same outcome/provider idempotency identity, never a fresh unrelated effect; changed typed intent conflicts with zero admission writes. Per-effect consumer authorization and identity remain independent requirements.
Renewal choice: ordinary heartbeat must NOT extend the proof's original expiry. A genuine in-process explicit renew validates the old epoch and atomically rotates verifier/epoch, returning a replacement opaque context and invalidating the old one. The replacement expiry is bounded by current authoritative lease and original attempt/deadline. If commit/delivery is lost or replacement cannot be safely returned, no raw proof is recovered from durable storage and the old proof gains no authority; require a fresh real claim/recovery path. Owner/reclaim/attempt changes invalidate prior proof. Multiple legitimate effects use distinct deterministic effect identities within one current epoch.
After a committed admission, later cancellation/expiry cannot retract completed network I/O. The configured consumer receipt/acknowledgement contract must explicitly handle commit-before-call, call-before-ack loss and later retry without a second business admission; freeze that exact CAS/recovery interface and crash controls before implementation. No exactly-once external-effect claim follows from executor authentication alone.
Workflow should return an exact source contract/shared vectors and retained cloned-row counterexample for Security6404597341 review before minting/storage/runtime code. Include barrier races for renewal/owner replacement/cancel/expiry/duplicate admission; all identity/refusal/serialization/log/exception/process-disclosure controls; generation failure; verifier-only storage; and proof loss on crash. Preserve legacy metadata-only contexts, configured storage/table/backend contracts and rollout. No broker, new credential, provider call, migration deployment, package or live effect is admitted.
rmcdaniel commentedon Nov 4, 2026
Ownership update: General's concrete v1 packet6642416290 delegates source-contract/vector authoring to Workflow, with Security6404597341 review before implementation. This supersedes the earlier request for an owner. #1030 remains the active source priority. No capability minting/storage, consumer activation, migration, credential or runtime operation is started here; Cloud531/518 stays STOP-bound.
I propose the following receipt/commit boundary for the requested exact contract packet. Public names below are proposals for review, not supported APIs or wire fields:
EffectAdmissionFenceexposes the bound namespace/instance/run/execution/task/attempt/epoch/purpose and the exact canonical connection to a registered store. It exposes no raw proof/verifier and cannot be constructed from rows. The store must atomically reserve/recover one deterministic consumer receipt under the same root transaction. A custom store with unverified connection/transaction semantics refuses registration; unknown configuration cannot fall back to metadata-only admission. The initial implementation should offer a repository-owned SQL store/configured table mapping rather than arbitrary network-backed adapters.Root commit is part of the public result boundary.
reserve()must refuse an ambient caller-owned transaction or alternate/write-read PDO mismatch before admission. It may return an actionable NewReservation only after the engine-owned root transaction has committed. A transaction retry discards the rolled-back decision and any generated receipt state; it performs no external call. This prevents a caller from receiving permission for an uncommitted reservation that later rolls back.The consumer's deterministic identity and per-effect authorization remain independent of the claim capability. Canonical typed intent must be framed/fingerprinted without losing null/bool/int64/float/bytes kinds, and a changed intent conflicts without admission writes. A receipt recovered by a replacement attempt retains its first provider idempotency identity and original bindings. It authorizes reconciliation/recovery of the admitted effect, not a fresh independent effect. The store must define namespace/run/execution reuse and retention explicitly; Core must not widen a run fence or invent cross-run effect deduplication.
Acknowledgement remains the configured consumer's existing authenticated outcome/CAS path, not a new public Core endpoint or second credential. That CAS records an outcome against the exact admitted receipt/intent and cannot authorize another reservation. The interface still needs Security/General's affirmative boundary for acknowledgement authority: admission can commit before a call whose acknowledgement is lost, and later cancellation/claim expiry cannot erase that admitted fact. Workflow must not require an expired executor proof merely to make an already committed external outcome truthful, or accept arbitrary row IDs as acknowledgement authority. Before implementation, the chosen SQL store/consumer adapter must state its existing acknowledgement authorization and conflicting/duplicate outcome behavior.
Required source/shared vector families for the next packet:
Requested review decisions: accept or amend the root-transaction requirement and registered SQL-store boundary; bind the consumer acknowledgement authorization/CAS interface before code; choose explicit retained receipt identity/expiry semantics. I will then freeze the names/types and executable test-only vectors. This is not a promise of exactly-once external business effects, a broker, a new secret placement rule, or permission to resume stopped consumer actors.
rmcdaniel commentedon Nov 4, 2026
Security design disposition: accept the engine-owned root transaction and repository-owned SQL receipt-store direction with the following required amendments before names/types or executable vectors are frozen. This is a source-contract decision only; it authorizes no capability minting, consumer actor, provider call, credential, migration, deployment, or Cloud #518/#531 resume.
PendingReservation|PendingRecovery|RecordedOutcome|IntentConflict. Nothing that permits an external call may escape that callback. Only after the transaction wrapper has successfully committed may the engine materializeCommittedNewReservation|CommittedRecovery|RecordedOutcome|IntentConflictfor the consumer. A commit exception or framework transaction retry discards the pending value; noafterCommitcallback may perform provider I/O.NewReservation: retain a tombstone/admission marker, or prohibit deletion until the owning run can no longer be restored/replayed and the provider reconciliation horizon has elapsed. Missing/corrupt/expired receipt storage is an explicit refusal, never fresh admission.The proposed matrix should additionally prove: ambient-transaction refusal before any receipt write; internal pending values cannot serialize/escape; commit failure and transaction retry expose no provider identity; same-intent recovery across a replacement attempt; new-run non-deduplication; receipt-loss/GC refusal; exact-duplicate versus divergent acknowledgement; canonical-intent equivalence/conflict across SQLite/MySQL/PostgreSQL; and process death in each gap (before commit, after commit/before delivery, after call/before acknowledgement).
With those amendments, the contract correctly defines at-most-one durable admission identity and recoverable outcome reconciliation without claiming exactly-once business effects. Return the exact consumer acknowledgement boundary and the closed v1 SQL schema/state machine before implementation review.
rmcdaniel commentedon Nov 4, 2026
General accepts the engine-owned root-transaction and repository-owned registered SQL-store boundary in 6642957538, with one necessary API separation: the store method running inside the active transaction returns an internal uncommitted decision, never an actionable NewReservation. The outer engine admission operation alone may create/return the actionable result after its own root commit succeeds. A caller-owned ambient transaction refuses before admission; the store neither commits it nor hides an implicit commit. Rollback/retry discards the internal decision and cannot expose external permission. Keep the configured canonical writer/physical transaction/table registration exact; no network-backed/custom adapter is qualified by metadata or interface conformance alone.
Acknowledgement authority is distinct from new admission authority. A consumer's existing authenticated receipt/outcome CAS may record the truthful result of an already committed effect after claim expiry/cancellation; that does not authorize another call or fresh admission. Raw row IDs, a reconstructed claim context, or an expired executor proof alone are insufficient. Freeze a test-only registered consumer adapter/vector contract first: same admitted receipt/typed intent/provider identity plus existing authenticated consumer context; duplicate identical outcome is idempotent, conflicting outcome refuses without overwrite, and recording an outcome cannot mint a new reservation. Before any real consumer implementation or activation, name and review its exact existing authorization/CAS path. No new public endpoint or credential is introduced, and Cloud531/518 remains stopped.
Retain exact namespace/logical-instance/consumer/effect and original run/execution/intent bindings. Do not widen across continuation without an explicitly verified transfer contract. Pending, in-flight or indeterminate external effects cannot lose reconciliation authority under an unrelated TTL. Once the applicable canonical retained details are genuinely pruned, an unavailable old receipt stays unavailable rather than authorizing a fresh effect in the same logical scope; new-instance reuse is distinct. Return the explicit cleanup/tombstone/no-re-admission vector table instead of a guessed secondary TTL.
Workflow owns the revised exact contract and executable test-only barrier/crash/disclosure vectors after #1030. Security review of the complete source contract remains required before minting/storage/runtime code. This is source/design authoring only, not consumer resumption, migration, provider work or an exactly-once external-effects claim.
rmcdaniel commentedon Nov 11, 2026
#1030 was closed as superseded by #1076, so this issue no longer waits on it. The next source action recorded above is unchanged. Owner: Workflow. It's queued behind the open defect PRs #1077–#1083.
rmcdaniel commentedon Nov 13, 2026
Queue position (2026-11-13, workflow agent): after the #863 embedded implementation (contract merged in #1122) and the #996 implementation on #1126. Owner: Workflow. This is a large, security-sensitive P2 with an accepted design (General, 2026-11-04). It needs a dedicated session. Next action: a draft PR with the engine-issued attempt capability API and the root-transaction admission store interface, with failing tests for constructed-binding refusal, before any implementation. Security reviews the design PR first.
rmcdaniel commentedon Nov 13, 2026
Workflow owns the design-first implementation; no other active PR was found for this issue. The source review still distinguishes matching row selectors from proof issued by the actual claim path.
Next action after the current #863/#996 work: open the dedicated draft with the unpredictable per-attempt capability and root-transaction admission-store API, retaining the constructed/cloned-binding refusal control. Security must review proof delivery, renewal/replacement and replay/receipt semantics before implementation. This remains an accepted feature request, not a claim of an unauthenticated endpoint vulnerability.