Sprint SPRINT_20260408_004 AUDIT-004 (retention enforcement).
AuditRetentionPurgeService BackgroundService enumerates tenants that
have any rows in timeline.unified_audit_events, then calls
timeline.purge_expired_audit_events(tenantId, dryRun) per tenant.
The SQL function honours per-classification retention windows and the
compliance_hold flag (legal holds pass through unaffected).
AuditRetentionPurgeOptions bound from the AuditRetentionPurge config
section: Enabled (default true), DryRun (default false), InitialDelay
(default 5 min), Interval (default 6h). Failures on a single tenant
are logged and do not stop the rest of the cycle.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprint SPRINT_20260408_004 AUDIT-004.
Schema (migration 005_audit_data_classification_retention.sql):
- ALTER timeline.unified_audit_events adds data_classification
(none|personal|sensitive|restricted, default 'none'), compliance_hold
(default false, exempts from purge), pii_redacted_at (null until
right-to-erasure redaction).
- New timeline.audit_retention_policies table holds per-tenant /
per-classification retention windows; seed row tenant_id='*' = platform
default (none/personal=365d, sensitive=730d, restricted=2555d ≈ 7y).
- Function resolve_audit_retention_days falls back tenant→platform→365d.
- Function purge_expired_audit_events iterates classes, honours
compliance_hold, supports dry-run counting.
- Function redact_actor_pii replaces actor_email/actor_ip/actor_user_agent
(+actor_name for personal/sensitive rows) with '[REDACTED]', preserves
actor_id so the content_hash chain stays intact.
Code:
- AuditDataClassifier implements the none/personal/sensitive/restricted
ladder: restricted (signer+attestor key/ceremony ops, cross-module
key_escrow actions) > sensitive (authority auth-protocol events) >
personal (actor email/ip/user_agent present). 16/16 unit tests pass.
- PostgresUnifiedAuditEventStore inserts include data_classification,
defaulting to AuditDataClassifier.Classify() when the payload's
precomputed value is absent. New RedactActorPiiAsync delegates to the
SQL function.
- UnifiedAuditEvent record gets an optional DataClassification property
so services that already classify events can bypass auto-classification.
- DELETE /api/v1/audit/actors/{actorId}/pii endpoint exposes
right-to-erasure, scoped to the new Timeline.Admin policy backed by
the timeline:admin scope (StellaOpsScopes.TimelineAdmin).
Remaining AUDIT-004 work: scheduled background purge host, Doctor
AuditReadinessCheck update to verify retention config, UI badges.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprint SPRINT_20260408_005 DEPRECATE-001 (JobEngine/ReleaseOrchestrator,
fifth service).
PostgresAuditRepository.AppendAsync now fans out to Timeline via the
optional IAuditEventEmitter after the local transaction commits. The
hash chain (content_hash, previous_entry_hash, sequence_number) stays
in the local audit_entries table as service-level chain-of-custody
evidence; Timeline receives only the summary event for cross-service
correlation, with the content hash surfaced as a detail field.
Same pattern as Authority/Policy/Notify/Scheduler dual-write:
fire-and-forget, optional DI, local write stays authoritative.
Remaining: Attestor dual-write (existing audit is already decorated
with .Audited() on endpoints — verifying the attestor audit log insert
path needs separate review).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprint SPRINT_20260408_005 DEPRECATE-001 (Scheduler, fourth service).
PostgresSchedulerAuditService.WriteAsync now fans out to Timeline
via the optional IAuditEventEmitter after the local scheduler.audit
row insert. Fire-and-forget, same pattern as Authority/Policy/Notify.
AuditActor is mapped to actor.{id, name, type} with kind -> type,
ActorId -> id, DisplayName -> name. Metadata tuples flatten as
`metadata.{key}` fields in Details. ScheduleId/RunId included.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprint SPRINT_20260408_005 DEPRECATE-001 (Notify, third service).
Same pattern as Authority + Policy dual-write: NotifyAuditRepository
now fans out to Timeline via the optional IAuditEventEmitter.
Fire-and-forget; local write stays authoritative.
Remaining DEPRECATE-001 services: Scheduler (ISchedulerAuditService),
JobEngine/ReleaseOrchestrator (PostgresAuditRepository.AppendAsync),
Attestor (audit log inserts).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprint SPRINT_20260408_005 DEPRECATE-001 (Policy, second service).
PolicyAuditRepository.CreateAsync now fans out to Timeline's unified
audit store via the optional IAuditEventEmitter (injected via
AddAuditEmission in Policy.Engine / Policy.Gateway Program.cs).
Same pattern as Authority dual-write in commit a947c8df6:
- Optional constructor dependency (default null) for DI compatibility
- Fire-and-forget emission wrapped in try/catch
- MapToTimelinePayload builds a UnifiedAuditEvent-compatible payload
with actor (UserId or "policy-system"), resource (audit.ResourceType,
audit.ResourceId), severity "info", and details including old/new
value strings plus the local audit id.
Remaining DEPRECATE-001 services: Notify, Scheduler, JobEngine,
Attestor dual-write on the same pattern.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprint SPRINT_20260408_005 DEPRECATE-001 (Authority, first service).
AuthorityAuditSink.WriteAsync now fans out to Timeline's unified audit
store via the optional IAuditEventEmitter (injected via AddAuditEmission
in Program.cs). The local authority.audit table write remains the
authoritative path; the Timeline emission is strictly fire-and-forget:
- Optional constructor dependency (default null) keeps existing tests
that construct the sink without the emitter working unchanged.
- Emission is wrapped in try/catch so any Timeline-side failure (DNS,
timeout, auth) is logged as a warning and never impacts the local
write or calling endpoint.
- MapToTimelinePayload builds a UnifiedAuditEvent-compatible payload
with actor (subject id/name/IP/UA), resource (authority_session
keyed by correlationId), severity derived from outcome, and event
details including client, reason, and event type.
Existing AuthorityAuditSinkTests (2/2) still pass — backward compat
verified via direct xUnit run.
Remaining DEPRECATE-001 work: Policy, Notify, Scheduler, JobEngine,
Attestor dual-write wiring on the same pattern. Tracked as follow-ups.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprint SPRINT_20260408_004_Timeline_unified_audit_sink AUDIT-002.
Second wave wiring — services outside the original 14-priority table that
own production audit-relevant surfaces:
- Router Gateway.WebService (ingress, claim mapping)
- Registry.TokenService (token issuance, plan admin)
- PacksRegistry.WebService (packs lifecycle)
- IssuerDirectory.WebService (issuer/subject identity, PII)
- ExportCenter.WebService (compliance bundle origin)
Same pattern: ProjectReference to StellaOps.Audit.Emission,
`using StellaOps.Audit.Emission;`, and `builder.Services
.AddAuditEmission(builder.Configuration);` placed after auth wiring.
All 5 projects build clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprint SPRINT_20260408_004_Timeline_unified_audit_sink AUDIT-002.
Wire the unified audit emitter in 7 webservices that were listed in the
AUDIT-002 priority table but missing the call:
- Concelier.WebService (priority 6a)
- Excititor.WebService (priority 6b)
- SbomService (priority 8)
- Graph.Api (priority 12)
- BinaryIndex.WebService (priority 14)
- Policy.Gateway (priority 3b)
- Notifier.WebService (priority 4b)
Each adds:
- ProjectReference to __Libraries/StellaOps.Audit.Emission
- using StellaOps.Audit.Emission;
- builder.Services.AddAuditEmission(builder.Configuration) in Program.cs
placed after auth wiring, before authorization policies.
AUDIT-002 completion criterion "AddAuditEmission() called in all 14+
service Program.cs files" is now met for the listed priority services.
Endpoint-level AuditActionAttribute decoration is a separate wave.
All 7 projects build clean against the existing Audit.Emission lib.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- scripts/test-targeted-xunit.ps1: replace @(x).Count checks with
[bool] coercion in Assert-FilterShape; StrictMode 'Latest' rejects
.Count on null even when wrapped in @().
- ConcelierInfrastructureRegistrationTests.AddConcelierPostgresStorage_
RegistersDurableObservationAndAffectedSymbolServices: wrap provider
in try/finally with DisposeAsync — ConcelierDataSource is
IAsyncDisposable only, so sync Dispose at `using` scope end throws.
Follow-up to SPRINT_20260419_027/028.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Follow-up to SPRINT_20260419_027_Concelier_durable_affected_symbol_runtime.
PostgresAdvisoryObservationStoreTests + PostgresAffectedSymbolStoreTests
covering the durable store behaviour.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Follow-up to SPRINT_20260419_027_Concelier_durable_affected_symbol_runtime
(REALPLAN-007-F still DOING).
Postgres-backed IAdvisoryObservationStore + IAffectedSymbolStore
implementations for the durable observation + affected-symbol
persistence path introduced by migration 008.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Follow-up to SPRINT_20260419_027_Concelier_durable_affected_symbol_runtime.
UnsupportedRuntimeWiringTests updated for the removed non-testing
UnsupportedAffectedSymbol registration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Follow-up to SPRINT_20260419_027_Concelier_durable_affected_symbol_runtime
(REALPLAN-007-F still DOING).
- Program.cs: register AddConcelierObservationPipeline and drop the
non-testing UnsupportedAffectedSymbolStore/Provider overrides — the
durable stores will replace them as REALPLAN-007-F lands.
- ConcelierInfrastructureRegistrationTests: contract test asserting
AddConcelierPostgresStorage registers durable observation lookup/sink
and affected-symbol store services.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprints SPRINT_20260418_025_Concelier_durable_mirror_bundle_import_runtime
and SPRINT_20260419_026_Concelier_mirror_import_allowlisted_root.
- MirrorBundleImportRuntimeService: durable PostgreSQL-backed live mirror
bundle importer replacing the testing-only in-memory path.
- Enforces an allowlisted import root (Mirror.ImportRoot) and rejects
bundle/trust-root paths that resolve outside it; relative paths resolve
against the configured root, not the process cwd.
_025 and _026 ship together because _026 extends the same live importer
introduced in _025; splitting leaves an unguarded filesystem reach.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprint SPRINT_20260417_023_Concelier_truthful_affected_symbol_runtime.
UnsupportedAffectedSymbolServices shim returning a clear
501/unsupported response until the durable affected-symbol backend ships.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprint SPRINT_20260417_022_AdvisoryAI_truthful_testing_only_runtime_fallback.
AdvisoryAiRuntimeStartupContractTests documenting the testing-only
in-memory fallback and its boundary versus the durable runtime.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprint SPRINT_20260417_002_JobEngine_scheduler_storage_compose_compatibility
(SCHEDULER-COMPAT-001 still DOING — sprint remains active).
Adds scheduler storage configuration adapter layer so the web host
accepts the compose-shaped storage configuration without manual remapping,
plus SchedulerStorageConfigurationTests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprint SPRINT_20260416_013_Authority_issuerdirectory_truthful_persistence_runtime.
IssuerDirectory.WebService Postgres persistence, options,
program wiring, tests. Sample config under etc/.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprint SPRINT_20260416_009_Notify_truthful_escalation_oncall_runtime.
PostgresEscalationRuntimeServices plus Notify + Notifier WebService
compat shims for escalation policy and on-call schedule service.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprint SPRINT_20260416_008_Notify_truthful_suppression_admin_runtime.
Postgres-backed suppression runtime services wired through the admin
runtime extension registered in the durable storage bootstrap.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprint SPRINT_20260416_006_BinaryIndex_symbols_truthful_manifest_runtime.
Symbols.Server: in-memory symbol source read repository with real
endpoints, program wiring, migrations, tests services.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New StellaOps.Workflow.ArtifactExporter project: a post-build console app that
reads the generator's bundled workflow registry from the compiled plugin DLL and
writes canonical JSON (authoritative, fail-build) plus SVG/PNG visual artifacts
(graceful warn) next to each *Workflow.cs source file. Replaces per-csproj
rendering boilerplate with a single targets import.
Key design choices:
- Console app invoked via <Exec>, not an MSBuild ITask DLL — easier to debug,
no rendering-lib loading into the MSBuild process.
- Links WorkflowRenderGraphCompiler.cs from Engine as a compiled file instead of
ProjectReference, avoiding EF Core + Oracle transitive deps in the tool.
- Parallel.ForEachAsync across workflows with file-lock + PID-sentinel
"latest-wins" cross-process coordinator (FileShare.None + FileOptions
.DeleteOnClose — no thread-affinity issues unlike Mutex).
- Hash-based cache: expected canonical-hash marker injected into
.definition.json; unchanged workflows skip re-render. First build 167
workflows in ~143s; no-change rebuild in ~0.1s.
- Atomic write-via-rename on every artifact.
Targets file (StellaOps.Workflow.ArtifactExporter.targets) plugins can import
to get: analyzer wiring + JSON/SVG/PNG export in one <Import>. Configurable via
StellaOpsWorkflowArtifactExport / StellaOpsWorkflowSkipSvg /
StellaOpsWorkflowSkipPng properties. Also surfaces CanonicalTemplates/*.json as
AdditionalFiles so the analyzer's fragment loader can inline runtime-loaded
fragments at compile time.
Verified: builds clean against upstream Abstractions/Contracts/Renderer.ElkSharp/
Renderer.Svg (net10.0, 0 warnings, 0 errors).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>