Payload/scripts/kaden/harness/harness-record-story-ids.ps1

#Requires -Version 5.1
<#
.SYNOPSIS
    Assign story ids to a slice projection; journals StoryIdsAssigned as a byproduct.

.DESCRIPTION
    Initialize-HarnessProjectionSlice accepts -StoryIds, but every runtime caller inside
    HarnessGateProjection.psm1 creates slice shells with -SliceId only, so story_ids stays
    an empty array for every slice after the one hand-seeded at feature start. This is the
    recorder that closes that gap: it journals a StoryIdsAssigned event (auditable, replay-safe)
    and rebuilds the projection, unioning the given ids into the slice's existing story_ids
    instead of overwriting them.

    Call this once per slice, as early as convenient (at plan approval or at first
    harness-turn-start.ps1), using the Story Coverage assigned in slice-plan.md. The
    validate-review-loop.ps1 MISSING_STORY_IDS check fails -RequireReviewReady when a
    slice's story_ids is still empty, so this recorder (not a hand-edit of .review-loop.json)
    is the only sanctioned way to satisfy that gate.

.EXAMPLE
    pwsh -File scripts/kaden/harness-invoke.ps1 harness-record-story-ids.ps1 -Feature kaden-distro-architecture -Slice repository-diagnostics -StoryIdsCsv "US-KDA-15" -ModelProvider cursor -ModelSlug claude-sonnet-5-thinking-medium
#>

[CmdletBinding()]
param(
    [Parameter(Mandatory = $true)]
    [string]$Feature,

    [Parameter(Mandatory = $true)]
    [string]$Slice,

    [Parameter(Mandatory = $true)]
    [string]$StoryIdsCsv,

    [string]$FeatureRoot = '',

    [string]$RepoRoot = '',

    [ValidateSet('cursor', 'copilot', 'other')]
    [string]$ModelProvider = 'copilot',

    [string]$ModelSlug = 'unknown',

    [ValidateSet('primary', 'subagent')]
    [string]$ModelRole = 'primary',

    [string]$Agent = 'atdd-developer',

    [string]$ParentAgent = '',

    [ValidateSet('cursor-task', 'copilot-agent', 'chat', 'harness-script')]
    [string]$Tool = 'harness-script',

    [switch]$AllowArbitraryFeatureRoot
)

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

Import-Module (Join-Path $PSScriptRoot 'lib\HarnessContract.psm1') -Force
Import-Module (Join-Path $PSScriptRoot 'lib\HarnessJournal.psm1') -Force
Import-Module (Join-Path $PSScriptRoot 'lib\HarnessGateProjection.psm1') -Force
Test-HarnessContractModuleReady

if ([string]::IsNullOrWhiteSpace($RepoRoot)) {
    $RepoRoot = Get-HarnessRepoRoot -StartPath $PSScriptRoot
}

$featureRootPath = Get-HarnessFeatureRoot -RepoRoot $RepoRoot -Feature $Feature -FeatureRootOverride $FeatureRoot -AllowArbitraryFeatureRoot:$AllowArbitraryFeatureRoot

$storyIds = [System.Collections.ArrayList]@()
foreach ($part in ($StoryIdsCsv -split ',')) {
    $t = $part.Trim()
    if ($t) { [void]$storyIds.Add($t) }
}
if ($storyIds.Count -eq 0) {
    throw '-StoryIdsCsv must contain at least one non-empty story id.'
}

$resolvedSlug = Get-HarnessResolvedModelSlug -ModelSlug $ModelSlug
$actor = New-HarnessJournalActor -Agent $Agent -ParentAgent $(if ($ParentAgent) { $ParentAgent } else { $null }) `
    -Tool $Tool -Provider $ModelProvider -Slug $resolvedSlug -Role $ModelRole
$activity = New-HarnessJournalActivity -Id (New-HarnessJournalActivityId) -Type 'other' -Name 'story-ids-assigned' -Role 'instant'

$journalEvent = Add-HarnessJournalEvent -FeatureRoot $featureRootPath -Feature $Feature -Slice $Slice `
    -Kind 'StoryIdsAssigned' -Activity $activity -Actor $actor -Outcome $null `
    -Summary "story ids assigned for $Slice" `
    -Payload ([pscustomobject]@{
        story_ids = @($storyIds.ToArray())
    }) `
    -Refs ([pscustomobject]@{
        amends_event_id = $null
        pair_event_id   = $null
        artifact        = $null
        command         = '.\scripts\kaden\harness-record-story-ids.ps1'
    })

$result = Update-HarnessGateProjectionFromJournal -FeatureRoot $featureRootPath -Feature $Feature -RepoRoot $RepoRoot -Write
$impl = Get-ImplementationPhase -LoopData $result
$targetSlice = @($impl.slices) | Where-Object { $_.id -eq $Slice } | Select-Object -First 1

[pscustomobject]@{
    operation         = 'record-story-ids'
    feature           = $Feature
    slice             = $Slice
    story_ids         = @($targetSlice.story_ids)
    journal_event_ids = @($journalEvent.event_id)
    status            = 'PASS'
} | ConvertTo-Json -Depth 10