Closes SPRINT_20260421_006 — all 4 tasks DONE. Full Tier 2c behavioral
verification per docs/qa/feature-checks/FLOW.md. Evidence directories
include per-route screenshots + tier2-ui-check JSON with PASS/FAIL/DEFERRED
assertions.
FE-QA-REL-001 — Release Control: 9/9 PASS
/environments/overview, /releases, /releases/deployments, /releases/bundles,
/releases/promotions, /releases/approvals, /releases/hotfixes,
/releases/investigation/timeline, /releases/workflows
FE-QA-REL-002 — Release Policy: 7/9 PASS, 2 DEFERRED
/ops/policy/{packs, governance, vex, simulation, governance/budget,
governance/profiles, vex/exceptions} — all PASS.
DEFERRED: /ops/policy/governance/audit (redirects to sprint-007-owned
/ops/operations/audit — scope lock), /ops/policy/governance/trust-weights
(tab URL doesn't persist — flagged as follow-up).
FE-QA-SEC-003 — Security: 10/10 effective PASS
Direct PASS: /security{,/images,/risk,/advisory-sources,/findings,
/vulnerabilities,/reachability}
Redirect PASS matching SEC-005/006/007 consolidation contracts:
/security/vex → /ops/policy/vex, /security/artifacts → /triage/artifacts,
/security/exceptions → /ops/policy/vex/exceptions.
FE-QA-RELSEC-004 — Retention coverage:
New e2e/routes/release-security-identity.e2e.spec.ts with 24 route-identity
assertions + 1 Release interaction guard. Uses auth.fixture.ts test-session
so CI does not require live Authority credentials.
Environmental gap surfaced (worked around in-session, NOT a code fix here):
stellaops_authority was missing the `default` tenant row, breaking setup-
wizard Admin bootstrap with FK users_tenant_id_fkey=(default) and causing
admin login to return invalid_grant. Manually seeded `default` into
authority.tenants and finalized the setup session via Platform Setup API.
Should be addressed in a follow-up Authority sprint — the default tenant
seed needs to land in startup migrations or StandardPluginRegistrar init.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the last open task in SPRINT_20260422_003. Persisted operator
enablement is now separated from runtime readiness so credential-gated
sources can show an explicit blocked state instead of collapsing into a
generic failed/disabled shape.
Readiness model:
- new SourceReadiness constants class: Disabled | Unsupported | Blocked | Ready
- ConfiguredAdvisorySourceStatus gains Readiness + BlockedReason alongside
existing SyncState (kept as backward-compatible alias)
- enabled = persisted operator intent (untouched)
- readiness = blocked when persisted-enabled and credentials/URIs missing
- blockedReason = free-form list of missing fields
- blockingReason.errorCode = SOURCE_CONFIG_REQUIRED for structured drill-down
Endpoint propagation:
- /status: persisted enabled=true kept; readiness=blocked; readyForSync=false
- /{id}/enable: 200 with readiness=blocked; sourceRegistry left disabled
until credentials land (pre-existing behaviour retained)
- /{id}/sync: 422 readiness=blocked + SOURCE_CONFIG_REQUIRED;
**connector never invoked**, no job run created
- /sync (batch): per-result outcome=blocked with readiness/errorCode/
blockedReason; excluded from totalTriggered; other sources proceed
- Transition: PUT /{id}/configuration with missing credential →
runtimeOptionsInvalidator.Invalidate → next /status flips to ready.
No disable/re-enable cycle needed.
Tests: 8 targeted xUnit methods via scripts/test-targeted-xunit.ps1,
8/8 pass. Includes: blocked status exposure, blocked-to-ready transition
on persisted credential, connector-not-invoked-when-blocked, plus 4
pre-existing SRC-CREDS-002 regression tests.
Docs:
- docs/modules/concelier/connectors.md — new "Blocked / sleeping
readiness state" section with field contract, per-endpoint behaviour
table, UI/CLI rendering guidance, resolution flow
- docs/modules/cli/guides/commands/db.md — short note under
`db connectors configure` cross-linking the connectors.md contract
Sprint SPRINT_20260422_003 archived — all 5 tasks DONE.
New fields are additive; existing UI types in
source-management.api.ts ignore unknown fields so no UI breakage. A
future FE pass can wire explicit readiness/blockedReason rendering.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bundled pre-session FE work from multiple sprints:
- SPRINT_20260420_003 FE web full-suite stabilization (partial)
- SPRINT_20260420_007 FE local stack reset + UI bootstrap
- SPRINT_20260421_002 FE advisory source consistency and visibility
- SPRINT_20260421_005 FE console route identity (partial)
Touches advisory-vex-sources catalog + API client, secret detection
components + tests, policy-studio, and various features. File-level
granularity preserved for blame.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SPRINT_20260422_001 (Notify compat surface restoration) — closes
NOTIFY-COMPAT-003 final criterion. notify-web rebuilt, 7/7 gateway probes
green post-rebuild (8th returns contracted 501 notify_overrides_not_supported).
Browser replay via direct Node+Playwright driver (MCP bridge rejected the
self-signed cert) confirmed Dashboard/Channels/Quiet Hours/Overrides/
Escalation/Throttle tabs render without runtime-unavailable banners. All 3
tasks DONE.
SPRINT_20260421_003 (Concelier advisory connector runtime alignment) —
all 7 tasks were already DONE before this session; archival is purely
administrative.
Evidence bundle at docs/qa/notify-compat-20260422/ includes EVIDENCE.md,
verify.mjs + verify_with_token.mjs, verify-results.json, and screenshots
for each verified tab.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brings Authority into §2.7 compliance. Previously AutoMigrate=true was set
in Program.cs but no runner was wired; 001_initial_schema.sql was
non-idempotent so wiring AddStartupMigrations against a pre-bootstrapped
DB crash-looped. Discovered during DEPRECATE-003 when the new drop
migration couldn't apply via Authority's own startup path.
Idempotency fixes in 001_initial_schema.sql:
- CREATE INDEX → CREATE INDEX IF NOT EXISTS (27 indexes)
- CREATE TRIGGER → DROP TRIGGER IF EXISTS + CREATE TRIGGER (3 triggers)
- CREATE POLICY → DROP POLICY IF EXISTS + CREATE POLICY (12 policies)
- CREATE TABLE / FUNCTION (OR REPLACE) / RLS ENABLE / role DO blocks were
already idempotent — left unchanged
Wiring:
- AddStartupMigrations("authority", "Authority", typeof(AuthorityDataSource)
.Assembly) called inside RegisterAuthorityServices (canonical
Signals/Scanner pattern).
- Stale options.AutoMigrate = true + options.MigrationsPath removed from
Program.cs.
- Migrations\_archived\** excluded from the EmbeddedResource glob.
Init script cleanup (migrations own schema authority now):
- 04-authority-schema.sql: 569 lines → 60 lines (schema shells + guarded
default-tenant seed fallback only; all DDL removed)
- 04b-authority-dedicated-schema.sql: same reduction for dedicated DB
Verification sequence — all PASS:
1. Green-field replay: 001 runs twice with zero semantic drift (pg_dump
diff shows only session restrict nonce).
2. Wire against pre-migrated volume: runner applies 001+002 in 209ms, no
crash-loop.
3. Wire + fresh schema: migrates 20 tables from empty in 395ms.
4. Idempotent restart: "Database is up to date", pure no-op.
Sprint SPRINT_20260422_003_Authority_auto_migration_compliance created
and archived in the same pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per-service audit read endpoints now source their data from
timeline.unified_audit_events instead of local repositories. Closes
DEPRECATE-002 in SPRINT_20260408_005.
New shared abstraction:
- ITimelineAuditQueryClient + HttpTimelineAuditQueryClient in
StellaOps.Audit.Emission, reusing AuditEmission:TimelineBaseUrl and a
named HttpClient "StellaOps.AuditQuery". 6 focused tests.
Endpoints redirected (module=X filter per service):
- Authority ConsoleAdminEndpointExtensions.ListAuditEvents (was a 501 stub)
- Policy Engine + Gateway GovernanceEndpoints (list + by-id, were 501 stubs)
- Notify /api/v1/notify/audit (was INotifyAuditRepository.ListAsync)
- ReleaseOrchestrator AuditEndpoints list / by-id / resource-history /
latest / sequence-range / summary. /verify stays local per Decision #2.
HttpUnifiedAuditEventProvider.GetEventsAsync neutered to empty list +
debug log; per-module legacy polling preserved under [Obsolete]
GetEventsLegacyAsync so CompositeUnifiedAuditEventProvider +
DurableAuditEmissionEndpointTests keep compiling.
Notify Program.cs also carries SPRINT_20260422_001 compat-endpoint wiring
(NotifyAdminCompatEndpoints mapping, INotifySimulationEngine DI, compat
simulation [FromBody]/[FromServices] binding) — bundled here because both
changes live in the same file.
Attestor has no public /audit read endpoint to redirect (write-only
IAttestorAuditSink); documented in sprint as N/A.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprint SPRINT_20260408_004. Execution log entry for the SbomService
backfill + Notifier wave E coverage extensions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprint SPRINT_20260408_004 AUDIT-002 (final decoration wave).
Five internal-only SbomService endpoints now emit audit events:
- POST /internal/sbom/events/backfill -> sbom.execute sbom_events_backfill
- POST /internal/sbom/inventory/backfill -> sbom.execute sbom_inventory_backfill
- POST /internal/sbom/resolver-feed/backfill -> sbom.execute sbom_resolver_feed_backfill
- POST /internal/sbom/retention/prune -> sbom.delete sbom_retention_prune
- POST /internal/orchestrator/watermarks -> sbom.update orchestrator_watermark
These are recovery / retention / watermark-manipulation routes that
previously slipped past audit because of their `internal` prefix. They
produce high-value audit events (data mutations that operators run
during incidents) and deserve the same coverage as the user-facing
surface.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprint SPRINT_20260408_004. Every per-service audit LIST endpoint
now advertises the Timeline successor link and Sunset 2027-10-19.
Remaining AUDIT-005 criteria (Timeline as SoT, no-data-loss) stay
gated on the 30-day production verification window.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprint SPRINT_20260408_004 AUDIT-005 (criterion 1 fully DONE).
- Authority `/console/admin/audit` list endpoint advertises Sunset
2027-10-19 and successor /api/v1/audit/events?modules=authority.
- Policy.Gateway `/api/v1/governance/audit/events` (list + by-id) do
the same with modules=policy; both still honour the PolicyAudit scope.
- EvidenceLocker `/api/v1/evidence/audit` advertises modules=evidencelocker.
- EvidenceLocker csproj picks up the Audit.Emission project reference.
Together with the earlier Notify + ReleaseOrchestrator coverage, every
per-service audit LIST endpoint now emits the RFC-style Sunset /
Deprecation / Link headers pointing at Timeline's unified endpoint.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprint SPRINT_20260408_004. Audit dashboard now renders the retention
tile and the log table shows classification / hold / redaction pills
alongside each event. All three criteria checked.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprint SPRINT_20260408_004 AUDIT-006 component layer.
audit-log-table:
- New Class. column between Severity and Actor shows the GDPR data
classification pill with module-aware tooltip explaining what each
class means (restricted > sensitive > personal > none).
- Compliance hold pill ("hold") surfaces when complianceHold is true.
- Redaction pill ("redacted") surfaces when piiRedactedAt is set, with
the redaction timestamp in the title attr.
- Detail panel mirrors the three new pieces of metadata as labelled rows.
- formatModule() table extended with the 13 newly-emitting modules.
- Styles for classification/hold/redacted pills reuse the existing status
colour tokens so dark/light themes inherit.
audit-log-dashboard:
- Fetches /api/v1/audit/retention-policies on open (catchError downgrades
to a non-blocking warning banner).
- Renders a per-tenant retention tile above the tabs with a 4-column grid
showing none/personal/sensitive/restricted retention days and a
footnote linking to docs/modules/timeline/audit-retention.
Type-check clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprint SPRINT_20260408_004. AUDIT-006 flipped TODO → DOING with the
model + client layer complete for all three criteria (module filter,
classification visibility, retention display). Angular component
renders (badges, overview tile) are a component follow-up since they
live in separate component templates the background agent owns.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprint SPRINT_20260408_004 AUDIT-006 (first slice).
- AuditModule union expanded with the 13 modules newly wired for unified
emission during AUDIT-002 waves A/B (graph, concelier, notifier, notify,
binaryindex, exportcenter, issuerdirectory, packsregistry, registry,
router, signer, timeline, evidencelocker).
- New AuditDataClassification type + optional AuditEvent.dataClassification,
complianceHold, and piiRedactedAt fields surface the AUDIT-004 state so
the dashboard can render classification badges and retention context.
- New AuditRetentionPolicies interface paired with
AuditLogClient.getRetentionPolicies() hitting Timeline's
/api/v1/audit/retention-policies endpoint for the retention display.
- AuditLogClient.redactActorPii() wraps the DELETE
/api/v1/audit/actors/{actorId}/pii endpoint for GDPR right-to-erasure.
- endpoints dictionary updated: new modules prefer the unified
/api/v1/audit/events endpoint as the fallback path, with tagged
module= query strings so operators can still slice by service.
Type-check clean. Table/dashboard component updates to render badges
follow in a later commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprint SPRINT_20260408_004. First criterion of AUDIT-005 met: two
per-service audit list endpoints now advertise Sunset/Deprecation/Link
headers pointing at Timeline's unified endpoint. Remaining two criteria
are gated on the 30-day production verification window.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprints SPRINT_20260408_004 AUDIT-005 + SPRINT_20260408_005
DEPRECATE-002 (early, non-waiting half).
- StellaOps.Audit.Emission gains a `DeprecatedAuditEndpoint` helper:
.DeprecatedForTimeline(sunset, successorLink) + a
DeprecationHeaderEndpointFilter that writes RFC-style Sunset,
Deprecation, and Link: <successor>; rel="successor-version" headers.
- Notify GET /api/v1/notify/audit + ReleaseOrchestrator
GET /api/v1/release-orchestrator/audit now advertise Sunset
2027-10-19 and link to /api/v1/audit/events?modules={notify|jobengine}.
Chain-verify / summary / single-entry lookups on ReleaseOrchestrator
are intentionally NOT deprecated — they serve service-level chain-of-
custody evidence (sprint Decision 2) that the unified store cannot
replace.
Both services build clean. Remaining per-service deprecation headers
(Authority /console/admin/audit, Policy /governance/audit/events,
EvidenceLocker /evidence/audit) follow the same pattern and can land
as a wave when those endpoints are touched for other reasons.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprint SPRINT_20260408_004. 2 of 3 criteria DONE: Timeline event pull
and chain-verification certificate are now included in AuditBundles.
DSSE manifest signing deferred as a follow-up (cross-service signer
handshake scope).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprint SPRINT_20260408_004 AUDIT-007 backend half.
- ITimelineAuditSource + HttpTimelineAuditSource talk to Timeline's
/api/v1/audit/events with pagination, tenant header forwarding, and a
hard MaxPages cap (default 200 ≈ 100k events). GetChainProofAsync
pulls /api/v1/audit/verify-chain for the bundle manifest.
- AuditBundleContentSelection gains an AuditEvents flag (default true)
and BundleSubjectRefDto gains a TenantId so the handler can route.
- AuditBundleJobHandler, when AuditEvents is selected and the source is
registered, writes NDJSON events to audit/events.ndjson and a chain
proof to audit/chain-proof.json, adds both as AUDIT_EVENTS /
AUDIT_CHAIN_PROOF artifacts with sha256 digests; failures are logged
and do not abort the bundle.
- AddTimelineAuditBundleSource DI helper binds to the
ExportCenter:TimelineAudit config section; the truthful runtime
registers it alongside the existing in-memory job handler.
Remaining AUDIT-007 work: DSSE-sign the audit bundle manifest via
Attestor integration — separate sprint because it requires cross-service
signer handshake.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprint SPRINT_20260408_004 AUDIT-004 reaches DONE with migration 005,
AuditDataClassifier (16 tests), RedactActorPiiAsync + DELETE endpoint,
AuditRetentionPurgeService, docs/modules/timeline/audit-retention.md,
and the new TimelineAuditRetentionCheck Doctor plugin all shipped.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprint SPRINT_20260408_004 AUDIT-004 — finishes the Doctor-side
completion criterion.
- Timeline: new GET /api/v1/audit/retention-policies endpoint returns
the effective retention window in days per classification for the
caller's tenant, with platform-default fallback applied via the
existing resolve_audit_retention_days SQL function.
- PostgresUnifiedAuditEventStore.GetRetentionPoliciesAsync queries the
function once per classification (none/personal/sensitive/restricted).
- Doctor Compliance plugin: new TimelineAuditRetentionCheck hits the
endpoint, compares each bucket against minimums
(none/personal ≥180d, sensitive ≥365d, restricted ≥1095d) and
warns with remediation steps when a tenant override drops too low.
Pairs with the existing AuditReadinessCheck but focuses on the
unified-store retention side.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprint SPRINT_20260408_004 AUDIT-004 documentation criterion.
docs/modules/timeline/audit-retention.md covers:
- Four-rung classification ladder and the "narrowest wins" rule
- Retention table structure, platform defaults, per-tenant overrides,
and legal holds via compliance_hold
- AuditRetentionPurgeService config + operator recommendations
- Right-to-erasure endpoint contract, hash-chain integrity guarantees,
and the idempotency semantics via pii_redacted_at
- Sequence-chain gap behaviour after purge and how chain verification
should window its checks
- Compliance checklist for operators
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprint SPRINT_20260408_004. AUDIT-004 flipped TODO → DOING with the
first three completion criteria checked. Migration 005, classifier,
retention purge host, and right-to-erasure endpoint all shipped across
commits 44c0e2b34..AUDIT-004 (migration + store + endpoint) and the
purge background host. Docs dossier + Doctor check deferred.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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. After AUDIT-002 wired Emission in all 14+
priority services, the original AUDIT-003 scope of "add more polling
targets" is no longer load-bearing. The remaining candidate modules
(Scanner, Scheduler, Integrations, Attestor) do not expose HTTP audit
endpoints — they rely on Emission. SbomService's ledger audit endpoint
is artifact-specific and does not fit the unified polling contract.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprint SPRINT_20260408_004 execution log entry for the 26+ new
.Audited() decorations across Graph, SbomService, Policy.Gateway,
Notifier, Concelier, Excititor (commits 4cbe58fc8 + 6c3ebff9d).
Combined with pre-existing decoration in Authority/Scanner/Policy.Engine/
Notify/JobEngine/Integrations/AdvisoryAI/EvidenceLocker/Attestor, the
codebase now has ~240 .Audited() call sites.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprint SPRINT_20260408_004. First completion criterion of AUDIT-002
("AddAuditEmission() called in all 14+ service Program.cs files") is
now DONE after waves A (commit b2b0c905b) + B (commit 981f4459a).
Remaining: endpoint-level AuditActionAttribute decoration, runtime
verification at /api/v1/audit/events, startup-time regression check.
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>
test-targeted-xunit.ps1 verified end-to-end against
SchedulerStorageConfigurationTests (3/3 pass via direct DLL exec).
QA flow + testing-practices docs updated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SchedulerStorageConfiguration ResolveConnectionString supports flat,
compose-nested, and legacy keys; web host fails fast with a clear
message listing all three. Targeted xUnit run of
SchedulerStorageConfigurationTests passes 3/3.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
All three tasks are now DONE per the 2026-04-19 execution log:
- ADV-SETUP-006 setup source probe/apply reflects real connectivity
- ADV-SETUP-007 enabled-but-broken advisory warning + auth helper
- ADV-SETUP-008 targeted Platform + Concelier xUnit verification
passed via the direct xUnit runner workflow.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>