Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
-- 0247_sealed_agent_task_and_child_run_payload.sql
--
-- Two public columns held resolved workflow secrets in plaintext because they are also what the platform executes:
--
-- * agent_run.task_jsonb — an agent.run goal that references {{team.X}} / {{wf.X}} / {{project.p.X}} is resolved
-- before staging, and the envelope was persisted whole. Viewers read it back as GetAgentRunQuery.Goal.
-- * workflow_run_request.normalized_payload_json — a flow.subworkflow child's inputs, resolved in the parent, are
-- the child's trigger payload. Viewers read it back as the child run's NormalizedPayload, and rerun copied it.
--
-- Both columns now hold a MASKED copy (every secret value the staging scope resolved replaced with [REDACTED]), and
-- the exact copy plus the list of secret values it embeds live in the encrypted column added here. The executor and
-- the child's scope build open that copy; every other reader keeps reading the masked column. The secret values ride
-- with the exact copy so the agent run's redactor and the child run's leak guard keep treating them as secrets.
--
-- NULLABLE with NO DEFAULT on purpose: a catalog-only ADD COLUMN on two hot tables, and NULL is exactly "nothing was
-- masked" — every row written before this migration, and every row whose payload carries no secret, keeps executing
-- from its public column unchanged.

ALTER TABLE agent_run ADD COLUMN IF NOT EXISTS sealed_task_ciphertext TEXT NULL;

COMMENT ON COLUMN agent_run.sealed_task_ciphertext IS
'Data Protection ciphertext of the exact AgentTask envelope and the workflow secret values it embeds. Set only when task_jsonb had a secret masked; the executor launches from it. Never returned by any read API.';

ALTER TABLE workflow_run_request ADD COLUMN IF NOT EXISTS sealed_payload_ciphertext TEXT NULL;

COMMENT ON COLUMN workflow_run_request.sealed_payload_ciphertext IS
'Data Protection ciphertext of the exact normalized payload and the inherited secret values it embeds (a flow.subworkflow child). Set only when normalized_payload_json had a secret masked; the engine builds the run scope from it. Never returned by any read API.';
7 changes: 7 additions & 0 deletions backend/src/CodeSpace.Core/Persistence/Entities/AgentRun.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ public class AgentRun : IEntity<Guid>, IAuditable
/// <summary>The full <c>AgentTask</c> envelope as JSON — the run's inputs, kept whole so the envelope can evolve without a migration.</summary>
public string TaskJson { get; set; } = "{}";

/// <summary>
/// The exact task envelope, encrypted, when <see cref="TaskJson"/> had a workflow secret masked (0247). Carries the
/// secret values the task embeds too, so the executor's redactor treats them as secrets. NULL — every pre-0247 row
/// and every task with no secret in it — means <see cref="TaskJson"/> is itself exact. Never projected by a read API.
/// </summary>
public string? SealedTaskCiphertext { get; set; }

/// <summary>The normalized <c>AgentRunResult</c> as JSON, written on completion. NULL while in-flight.</summary>
public string? ResultJson { get; set; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,13 @@ public class WorkflowRunRequest : IEntity<Guid>
/// </summary>
public string NormalizedPayloadJson { get; set; } = "{}";

/// <summary>
/// The exact payload, encrypted, when <see cref="NormalizedPayloadJson"/> had an inherited secret masked (0247 — a
/// <c>flow.subworkflow</c> child whose inputs resolved a parent secret). Carries those secret values too, so the run
/// keeps treating them as secrets. NULL means <see cref="NormalizedPayloadJson"/> is itself exact. Never projected.
/// </summary>
public string? SealedPayloadCiphertext { get; set; }

/// <summary>IP, user-agent, schedule fire-time, etc. — anything that's about the request itself rather than its content.</summary>
public string RequestMetadataJson { get; set; } = "{}";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public void Configure(EntityTypeBuilder<AgentRun> builder)
builder.Property(r => r.IterationKey).HasColumnName("iteration_key");

builder.Property(r => r.TaskJson).HasColumnName("task_jsonb").HasColumnType("jsonb");
builder.Property(r => r.SealedTaskCiphertext).HasColumnName("sealed_task_ciphertext");
builder.Property(r => r.ResultJson).HasColumnName("result_jsonb").HasColumnType("jsonb");

// P3.1a — the harness-native CLI session/thread id, promoted from result_jsonb so the rerun CONTINUE lookup
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public void Configure(EntityTypeBuilder<WorkflowRunRequest> builder)
builder.Property(r => r.ActivationSnapshotJson).HasColumnName("activation_snapshot_json").HasColumnType("jsonb");
builder.Property(r => r.RawHeadersRedactedJson).HasColumnName("raw_headers_redacted_json").HasColumnType("jsonb");
builder.Property(r => r.NormalizedPayloadJson).HasColumnName("normalized_payload_json").HasColumnType("jsonb");
builder.Property(r => r.SealedPayloadCiphertext).HasColumnName("sealed_payload_ciphertext");
builder.Property(r => r.RequestMetadataJson).HasColumnName("request_metadata_json").HasColumnType("jsonb");
builder.Property(r => r.VerificationResultJson).HasColumnName("verification_result_json").HasColumnType("jsonb");
}
Expand Down
Loading
Loading