feat(campaign): Campaign declares steering intent (arc A: A0 + A1)#424
Open
xmap wants to merge 3 commits into
Open
feat(campaign): Campaign declares steering intent (arc A: A0 + A1)#424xmap wants to merge 3 commits into
xmap wants to merge 3 commits into
Conversation
The Campaign aggregate (arc A) needs to declare a campaign's steering INTENT
(steering_objective / steering_space) so an across-Run steerer can derive the next
Run, but tach forbids cora.campaign from importing cora.operation.ports where
those VOs live (cora.campaign depends_on = {infrastructure, shared,
campaign.aggregates, run.aggregates}).
Move the optimizer- and BC-agnostic INTENT value objects (SteeringObjective,
SteeringSpace, SteeringAxis, SteeringObjectiveKind, SteeringPoint) to a new
cora.shared.steering home (an allowed campaign dependency), re-exported from
cora.operation.ports.decide_port so every existing Operation importer stays
stable. Mirrors the DecisionConfidenceSource -> cora.shared.decision_signals
relocation done for the same tach reason. The ADVICE side of the seam
(SteeringAdvice / Verdict / Evidence / Observation / Budget, the Decide*Error
families, the DecidePort Protocol, objective_is_satisfied) stays in decide_port:
it depends on the Operation BC's own Measurement / ArtifactRef / ActuationKind and
only Operation consumes it.
Mechanical, behavior-preserving: the VOs are frozen dataclasses with no Operation
dependency. tach clean; full fast tier 38928 passed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Arc A slice A1: promote the Campaign aggregate from a passive run-id envelope
toward the owner of across-Run steering INTENT. An operator (or a future steerer)
declares what good means (a SteeringObjective) and where to look (a SteeringSpace)
on the Campaign; a later across-Run selection seam (A2) reads that intent to
derive the next Run.
Additive, no migration:
- Two nullable state fields on Campaign: steering_objective / steering_space
(default None), imported from cora.shared.steering (A0 relocation; tach forbids
campaign -> operation.ports). Legacy CampaignRegistered streams fold to None.
- New CampaignSteeringDeclared event (objective + space) with symmetric serde
(enum .value <-> kind, choices tuple <-> list, nullable axis bounds via .get();
bad-enum ValueError wrapped via extra=(ValueError,)).
- declare_campaign_steering slice (command/handler/decider/route/tool): PUT
semantics (re-declare overwrites), guards status in {Planned, Active} else
CampaignCannotDeclareSteeringError (409), validates non-empty space + a
Satisfy objective carrying its target else InvalidCampaignSteeringError (400).
REST POST /campaigns/{id}/declare-steering (204); MCP declare_campaign_steering.
Two load-bearing design rules honored (keep the future GenerationStrategy
extraction clean): (1) Slim Aggregate -- only declarative INTENT lands on
Campaign, NEVER per-step selection/optimizer state (that lives in the Decision
log); (2) fold-symmetry -- the evolver threads both new fields through ALL seven
non-genesis arms so a later Start/Hold/Resume/Close/Abandon/RunAdded/RunRemoved
never resets declared steering (guarded by a fold-preservation test).
Gate-reviewed: naming-r3 OK; adversarial SAFE (fold-symmetry + legacy-fold both
correct + tested); a phase tag the build introduced was reworded. Full fast tier
+ both new contract tests: 39042 passed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Coverage reportClick to see where and how coverage changed
This report was generated by python-coverage-comment-action |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Arc A, first two slices: promote the Campaign aggregate from a passive run-id
envelope toward the owner of across-Run steering INTENT, so a future across-Run
steerer can derive the next Run from what-good-means + where-to-look declared on
the Campaign. Grounded by two deep-research passes (the within/across two-level
split is sound; selection is optimizer-derived; Campaign owns the INTENT, the
Decision log owns the per-step selection).
A0 (
d46143ffa3): relocate the optimizer- and BC-agnostic steering INTENTvalue objects (SteeringObjective / SteeringSpace / SteeringAxis /
SteeringObjectiveKind / SteeringPoint) to
cora.shared.steering, re-exportedfrom
cora.operation.ports.decide_portso every Operation importer staysstable. Required because
cora.campaigncannot importcora.operation.ports(tach). Mirrors the earlier
DecisionConfidenceSource -> cora.sharedmove.Mechanical, behavior-preserving.
A1 (
d9705e9e0d): Campaign declares its steering intent. Two nullable statefields (
steering_objective/steering_space, default None, additive no-migration), a new
CampaignSteeringDeclaredevent with symmetric serde, and adeclare_campaign_steeringslice (RESTPOST /campaigns/{id}/declare-steering-> 204; MCP
declare_campaign_steering). PUT semantics; guards status in{Planned, Active}, non-empty space, and a Satisfy objective carrying its target.
Two load-bearing design rules keep the future GenerationStrategy extraction clean:
(1) Slim Aggregate -- only declarative INTENT on Campaign, NEVER per-step
selection/optimizer state (that lives in the Decision log); (2) fold-symmetry
-- the evolver threads both new fields through all seven non-genesis arms so a
later lifecycle/membership transition never resets declared steering (guarded by a
fold-preservation test).
Deferred (next): A2 across-Run selection seam
A2 (build SteeringEvidence from a Campaign's completed-Run results + advise +
record a Decision) is deferred: its READ PATH is undesigned and would invent two
conventions on a guess -- the point a Run ran at lives in free-form
effective_parameters(not axis-keyed), and the objective scalar lives in theRun's observation logbook (read by channel_name). Per the layer's "design WITH the
real consumer, not speculatively" stance, A2's read path will be designed WITH
gpCAM (or a concrete steered-Run-records-its-point convention).
Test plan
Start/Hold/Resume/Close/Abandon/RunAdded/RunRemoved; decider guards (status,
empty space, Satisfy-without-target); decider PBT; REST + MCP contract tests
🤖 Generated with Claude Code