@prism-d1/cli 1.0.91 → 1.0.93

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -192,7 +192,7 @@ Three details, each of which cost a failed deployment to learn:
192
192
  The image is a single mise-based base (~750 MB, 37% of the 2 GB cap) that includes
193
193
  the Strands agent SDK, boto3, and the full orchestrator (`run.py` + `agent.py`). It
194
194
  ships a version *manager* and installs whatever a repository pins in `.tool-versions`
195
- at session start. Default model: `us.anthropic.claude-sonnet-5`.
195
+ at session start. Default model: `us.anthropic.claude-sonnet-4-5-20250929-v1:0`.
196
196
 
197
197
  Because the agent code is in the image, updating it requires an image rebuild
198
198
  (`deploy-harness.sh --tag v2`). This is a trade-off for simplicity: the workflow has
@@ -812,7 +812,7 @@ Deployed, through the harness (v13, Claude Sonnet 5):
812
812
  ```
813
813
  outcome patched
814
814
  verified True npm test passes in the microVM
815
- model us.anthropic.claude-sonnet-5
815
+ model us.anthropic.claude-sonnet-4-5-20250929-v1:0
816
816
  tokens ~95K in / ~4.5K out
817
817
  cost ~$0.35 per issue fix
818
818
  files src/routes/tasks.ts + tests/tasks.test.ts
@@ -26,13 +26,35 @@ from config import ConfigError, load_config
26
26
  from system_prompt import build_system_prompt, build_task_prompt
27
27
 
28
28
 
29
+ def _unwrap_issue(payload: dict) -> dict:
30
+ """Return the issue object, whether or not it is nested under an `issue` key.
31
+
32
+ Two producers feed `--issue` and they disagree on shape:
33
+
34
+ * eval fixtures (`.coding-agent/fixtures/*.json`) are flat --
35
+ `{"number": 1, "title": ..., "body": ...}`
36
+ * the CI workflow wraps, because `gh issue view --json ... | jq '{issue: .}'`
37
+ produces `{"issue": {"number": 9, ...}}`
38
+
39
+ Reading `.get("title")` off the wrapper returns None, so the agent was handed
40
+ `Fix issue #?: (no title)` with `(no description provided)` as its entire task.
41
+ It then did the only thing it could -- went looking for something to fix --
42
+ and produced a correct patch for a bug nobody asked about. The scope-discipline
43
+ constraints in the system prompt cannot help here: there was no scope to stay
44
+ inside. That is why this normalises instead of trusting either caller.
45
+ """
46
+ if isinstance(payload, dict) and "issue" in payload and isinstance(payload["issue"], dict):
47
+ return payload["issue"]
48
+ return payload
49
+
50
+
29
51
  def _load_issue(args: argparse.Namespace) -> dict:
30
52
  """Resolve the issue from a fixture file, a GitHub event payload, or flags."""
31
53
  if args.issue:
32
54
  path = Path(args.issue)
33
55
  if not path.exists():
34
56
  raise SystemExit(f"Issue file not found: {path}")
35
- return json.loads(path.read_text())
57
+ return _unwrap_issue(json.loads(path.read_text()))
36
58
 
37
59
  if args.github_event:
38
60
  path = Path(args.github_event)
@@ -104,7 +104,7 @@ class AgentConfig:
104
104
  agent_email: str = "prism-agent@example.com"
105
105
  agent_name: str = "PRISM Coding Agent"
106
106
  max_attempts: int = 3
107
- model_id: str = "us.anthropic.claude-sonnet-5"
107
+ model_id: str = "" # Set by --model-id; no default — deploy-harness.sh owns the model choice
108
108
  region: str = "us-west-2"
109
109
  project_type: str = "unknown"
110
110
  source: str = "detected" # "config" | "detected" | "none"
@@ -54,6 +54,121 @@ def find_existing(client, name: str) -> str | None:
54
54
  return None
55
55
 
56
56
 
57
+ ROLE_PROPAGATION_TIMEOUT_SECONDS = 180
58
+ ROLE_PROPAGATION_POLL_SECONDS = 5
59
+
60
+
61
+ def _is_role_not_yet_visible(exc: ClientError) -> bool:
62
+ """True when a ValidationException is the IAM propagation race, not a bad role.
63
+
64
+ AgentCore validates that it can assume the execution role, and a role created
65
+ seconds earlier is not yet visible to it:
66
+
67
+ ValidationException: Role validation failed for '...'. Please verify that
68
+ the role exists and its trust policy allows assumption by this service
69
+
70
+ The message is identical whether the role is genuinely misconfigured or merely
71
+ too new, so this matches on the text and lets the timeout below decide. A
72
+ permanently broken trust policy simply exhausts the retries and reports the
73
+ original error.
74
+ """
75
+ err = exc.response.get("Error", {})
76
+ if err.get("Code") != "ValidationException":
77
+ return False
78
+ message = err.get("Message", "").lower()
79
+ return "role validation failed" in message or "trust policy" in message
80
+
81
+
82
+ def _call_with_role_propagation_retry(label: str, fn):
83
+ """Run fn(), retrying while AgentCore still cannot see a freshly created role.
84
+
85
+ deploy-harness.sh creates the execution role and then immediately creates the
86
+ harness. On an account where the role already exists that gap is irrelevant, so
87
+ every re-run succeeds and this race stays invisible -- which is why it first
88
+ surfaced during workshop provisioning into a brand new account. Measured
89
+ evidence: a 55-second gap succeeded, a 6-second gap failed.
90
+ """
91
+ deadline = time.monotonic() + ROLE_PROPAGATION_TIMEOUT_SECONDS
92
+ announced = False
93
+ while True:
94
+ try:
95
+ return fn()
96
+ except ClientError as exc:
97
+ if not _is_role_not_yet_visible(exc) or time.monotonic() >= deadline:
98
+ raise
99
+ if not announced:
100
+ print(f" {label}: execution role not visible to AgentCore yet, "
101
+ f"retrying for up to {ROLE_PROPAGATION_TIMEOUT_SECONDS}s",
102
+ file=sys.stderr)
103
+ announced = True
104
+ time.sleep(ROLE_PROPAGATION_POLL_SECONDS)
105
+
106
+
107
+ def for_update(client, spec: dict) -> dict:
108
+ """Adapt a CreateHarness spec to the shape UpdateHarness expects.
109
+
110
+ The two operations do not take the same input. Several members that
111
+ CreateHarness accepts directly are wrapped in an `optionalValue` envelope on
112
+ UpdateHarness, so that clearing a field can be distinguished from omitting
113
+ it. At the time of writing that applies to `environmentArtifact`,
114
+ `authorizerConfiguration` and `memory` -- passing the create shape fails
115
+ validation client-side with:
116
+
117
+ Unknown parameter in environmentArtifact: "containerConfiguration",
118
+ must be one of: optionalValue
119
+
120
+ The set of wrapped members is read off the service model rather than
121
+ hardcoded, so adding one of the other wrapped fields to `spec` later cannot
122
+ silently reintroduce this failure.
123
+ """
124
+ members = client.meta.service_model.operation_model(
125
+ "UpdateHarness"
126
+ ).input_shape.members
127
+ wrapped = {
128
+ name
129
+ for name, shape in members.items()
130
+ if "optionalValue" in getattr(shape, "members", {})
131
+ }
132
+ return {
133
+ key: {"optionalValue": value} if key in wrapped else value
134
+ for key, value in spec.items()
135
+ }
136
+
137
+
138
+ def unwrap_harness(response: dict) -> dict:
139
+ """Return the harness object from any single-harness response envelope.
140
+
141
+ CreateHarness, UpdateHarness and GetHarness all nest the object under a
142
+ `harness` key -- they share one `Harness` output shape. Only ListHarnesses
143
+ differs, returning flat objects in `harnesses`.
144
+
145
+ This is a function rather than an inline `.get("harness")` because the same
146
+ envelope has now been missed twice. First on the read path, where reading
147
+ `status` off the top level of a get_harness response yielded None forever, so
148
+ the wait below span its full timeout and reported failure on a harness that
149
+ had been READY the whole time. Then on the create path, where
150
+ `created["harnessId"]` raised KeyError: 'harnessId' -- invisible until a
151
+ provisioning run actually created a harness through this script rather than
152
+ updating one that already existed.
153
+ """
154
+ return response.get("harness", {})
155
+
156
+
157
+ def harness_id_from(response: dict, operation: str) -> str:
158
+ """Pull harnessId out of a create/update response, or say what came back.
159
+
160
+ A bare KeyError names the missing key but not the envelope it was missing
161
+ from, which is a poor trade when the call costs a full provisioning run.
162
+ """
163
+ harness = unwrap_harness(response)
164
+ harness_id = harness.get("harnessId")
165
+ if not harness_id:
166
+ sys.exit(f"{operation} returned no harnessId.\n"
167
+ f" envelope keys: {sorted(response)}\n"
168
+ f" harness keys: {sorted(harness) or '(harness key absent)'}")
169
+ return harness_id
170
+
171
+
57
172
  def describe(client, harness_id: str) -> dict:
58
173
  """Fetch one harness, unwrapping the response envelope.
59
174
 
@@ -64,24 +179,53 @@ def describe(client, harness_id: str) -> dict:
64
179
  been READY the whole time. The same mistake also made `maxIterations` look
65
180
  unset on a harness that had it set to 40.
66
181
  """
67
- return client.get_harness(harnessId=harness_id).get("harness", {})
182
+ return unwrap_harness(client.get_harness(harnessId=harness_id))
183
+
184
+
185
+ def _report_failure(harness_id: str, harness: dict, headline: str):
186
+ """Exit with everything the harness already told us about why it failed.
187
+
188
+ `failureReason` is a member of the Harness shape and is the only field that
189
+ says *why* provisioning failed. Reporting the status alone -- which is what
190
+ this did -- costs a whole provisioning run to learn the word "CREATE_FAILED",
191
+ and the harness then has to be queried by hand to find out anything useful.
192
+ The image URI and execution role are echoed too because they are the two
193
+ inputs that most often break a container harness after the control-plane call
194
+ has already succeeded.
195
+ """
196
+ reason = harness.get("failureReason") or "(no failureReason reported)"
197
+ image = (harness.get("environmentArtifact", {})
198
+ .get("containerConfiguration", {})
199
+ .get("containerUri", "(none)"))
200
+ sys.exit(f"{headline}\n"
201
+ f" reason: {reason}\n"
202
+ f" image: {image}\n"
203
+ f" role: {harness.get('executionRoleArn', '(none)')}\n"
204
+ f" status: {harness.get('status', 'unknown')}\n"
205
+ f" full detail:\n"
206
+ f" aws bedrock-agentcore-control get-harness \\\n"
207
+ f" --harness-id {harness_id} --query harness")
68
208
 
69
209
 
70
210
  def wait_ready(client, harness_id: str) -> None:
71
211
  deadline = time.monotonic() + READY_TIMEOUT_SECONDS
72
212
  last = ""
213
+ harness: dict = {}
73
214
  while time.monotonic() < deadline:
74
- status = describe(client, harness_id).get("status", "")
215
+ harness = describe(client, harness_id)
216
+ status = harness.get("status", "")
75
217
  if status == "READY":
76
218
  return
77
219
  if status in {"CREATE_FAILED", "UPDATE_FAILED", "FAILED"}:
78
- sys.exit(f"harness {harness_id} reached {status}")
220
+ _report_failure(harness_id, harness,
221
+ f"harness {harness_id} reached {status}")
79
222
  if status != last:
80
223
  print(f" status {status or 'unknown'}", file=sys.stderr)
81
224
  last = status
82
225
  time.sleep(POLL_SECONDS)
83
- sys.exit(f"harness {harness_id} did not reach READY within "
84
- f"{READY_TIMEOUT_SECONDS}s (last status {last or 'unknown'})")
226
+ _report_failure(harness_id, harness,
227
+ f"harness {harness_id} did not reach READY within "
228
+ f"{READY_TIMEOUT_SECONDS}s (last status {last or 'unknown'})")
85
229
 
86
230
 
87
231
  def main() -> int:
@@ -115,19 +259,40 @@ def main() -> int:
115
259
  try:
116
260
  if existing:
117
261
  print(f" updating {existing}", file=sys.stderr)
118
- client.update_harness(harnessId=existing, **spec)
262
+ _call_with_role_propagation_retry(
263
+ "update", lambda: client.update_harness(harnessId=existing, **for_update(client, spec)))
119
264
  harness_id = existing
120
265
  else:
121
266
  print(f" creating {name}", file=sys.stderr)
122
- created = client.create_harness(harnessName=name, **spec)
123
- harness_id = created["harnessId"]
267
+ created = _call_with_role_propagation_retry(
268
+ "create", lambda: client.create_harness(harnessName=name, **spec))
269
+ harness_id = harness_id_from(created, "create_harness")
124
270
  except ClientError as exc:
125
271
  code = exc.response.get("Error", {}).get("Code", "")
126
272
  message = exc.response.get("Error", {}).get("Message", str(exc))
127
273
  if code == "AccessDeniedException":
274
+ # The hint names both halves on purpose. The SDK calls CreateHarness /
275
+ # UpdateHarness / GetHarness / ListHarnesses, but IAM evaluates the
276
+ # AgentRuntime action underneath each one, so a policy granting only the
277
+ # *Harness names is denied on *AgentRuntime -- which is what the earlier
278
+ # version of this message sent people chasing.
279
+ sys.exit(f"{code}: {message}\n"
280
+ f" The calling identity needs both halves of each Harness call:\n"
281
+ f" bedrock-agentcore:CreateHarness + :CreateAgentRuntime\n"
282
+ f" bedrock-agentcore:UpdateHarness + :UpdateAgentRuntime\n"
283
+ f" bedrock-agentcore:GetHarness + :GetAgentRuntime\n"
284
+ f" bedrock-agentcore:ListHarnesses + :ListAgentRuntimes\n"
285
+ f" and iam:PassRole for {role}.\n"
286
+ f" IAM authorizes the AgentRuntime action, not the Harness one --\n"
287
+ f" read the action name in the message above, not this list.")
288
+ if code == "ValidationException" and "role" in message.lower():
128
289
  sys.exit(f"{code}: {message}\n"
129
- f" The calling identity needs bedrock-agentcore:CreateHarness /\n"
130
- f" UpdateHarness / ListHarnesses, and iam:PassRole for {role}.")
290
+ f" {role} was still not assumable by AgentCore after "
291
+ f"{ROLE_PROPAGATION_TIMEOUT_SECONDS}s, so this is not the usual\n"
292
+ f" propagation delay. Check the trust policy actually allows\n"
293
+ f" bedrock-agentcore.amazonaws.com to sts:AssumeRole:\n"
294
+ f" aws iam get-role --role-name {role.rsplit('/', 1)[-1]} \\\n"
295
+ f" --query Role.AssumeRolePolicyDocument")
131
296
  sys.exit(f"{code}: {message}")
132
297
 
133
298
  wait_ready(client, harness_id)
@@ -31,7 +31,7 @@ TAG="v1"
31
31
  NAME="PrismCodingAgent"
32
32
  ECR_REPO="prism/coding-agent-harness"
33
33
  ROLE_NAME="PrismCodingAgentHarnessRole"
34
- MODEL_ID="us.anthropic.claude-sonnet-4-5-20250929-v1:0"
34
+ MODEL_ID="us.anthropic.claude-sonnet-5"
35
35
  # Matches agentcore.contract.MAX_ITERATIONS. The client passes maxIterations on
36
36
  # every InvokeHarness call, so this is only the fallback for callers that do not.
37
37
  MAX_ITERATIONS=100
@@ -102,7 +102,7 @@ docker buildx build \
102
102
  --tag "${IMAGE}" \
103
103
  --file "${HERE}/Dockerfile" \
104
104
  --push \
105
- "${HERE}"
105
+ "${AGENT_DIR}"
106
106
  echo " pushed ${IMAGE}"
107
107
 
108
108
  # ---- 3. Execution role -------------------------------------------------------
@@ -135,7 +135,13 @@ POLICY=$(cat <<JSON
135
135
  "bedrock-agentcore:RetrieveMemoryRecords","bedrock-agentcore:ListMemoryRecords",
136
136
  "bedrock-agentcore:GetMemoryRecord","bedrock-agentcore:GetMemory",
137
137
  "bedrock-agentcore:CreateMemory","bedrock-agentcore:ListMemories"],
138
- "Resource":"*"}
138
+ "Resource":"*"},
139
+ {"Sid":"SsmTelemetryConfig","Effect":"Allow","Action":[
140
+ "ssm:GetParameter","ssm:GetParameters"],
141
+ "Resource":"arn:aws:ssm:${REGION}:${ACCOUNT}:parameter/prism/d1/*"},
142
+ {"Sid":"SecretsManagerAgentCreds","Effect":"Allow","Action":[
143
+ "secretsmanager:GetSecretValue"],
144
+ "Resource":"arn:aws:secretsmanager:${REGION}:${ACCOUNT}:secret:prism-d1-*"}
139
145
  ]}
140
146
  JSON
141
147
  )
@@ -167,8 +173,8 @@ HARNESS_ARN=$(
167
173
  )
168
174
  echo " ${HARNESS_ARN}"
169
175
 
170
- # ---- 5. Write the ARN to SSM so it is discoverable without manual copying ----
171
- echo "==> SSM parameter"
176
+ # ---- 5. Write the ARN and model to SSM so they are discoverable without manual copying ----
177
+ echo "==> SSM parameters"
172
178
  aws ssm put-parameter \
173
179
  --name /prism/d1/harness-arn \
174
180
  --value "${HARNESS_ARN}" \
@@ -176,6 +182,13 @@ aws ssm put-parameter \
176
182
  --overwrite \
177
183
  --description "AgentCore harness ARN — written by deploy-harness.sh" >/dev/null
178
184
  echo " /prism/d1/harness-arn = ${HARNESS_ARN}"
185
+ aws ssm put-parameter \
186
+ --name /prism/d1/harness-model \
187
+ --value "${MODEL_ID}" \
188
+ --type String \
189
+ --overwrite \
190
+ --description "Model ID used by the coding agent — written by deploy-harness.sh" >/dev/null
191
+ echo " /prism/d1/harness-model = ${MODEL_ID}"
179
192
 
180
193
  cat <<EOF
181
194
 
@@ -113,6 +113,22 @@ jobs:
113
113
  fi
114
114
  echo "arn=${ARN}" >> "$GITHUB_OUTPUT"
115
115
  echo "Harness: ${ARN}"
116
+ # Resolve the model. SSM is the primary source (written by deploy-harness.sh,
117
+ # readable by the OIDC role). Fallback: extract from the harness API (requires
118
+ # bedrock-agentcore:GetAgentRuntime which not all roles have).
119
+ MODEL_ID=$(aws ssm get-parameter --name /prism/d1/harness-model \
120
+ --query Parameter.Value --output text 2>/dev/null || echo "")
121
+ if [[ -z "${MODEL_ID}" ]]; then
122
+ HARNESS_ID=$(echo "${ARN}" | grep -oP '[^/]+$')
123
+ MODEL_ID=$(python3 -c "
124
+ import boto3, json
125
+ c = boto3.client('bedrock-agentcore-control', region_name='${PRISM_AWS_REGION}')
126
+ h = c.get_harness(harnessId='${HARNESS_ID}')['harness']
127
+ print(h.get('model',{}).get('bedrockModelConfig',{}).get('modelId',''))
128
+ " 2>/dev/null || echo "")
129
+ fi
130
+ echo "model=${MODEL_ID}" >> "$GITHUB_OUTPUT"
131
+ echo "Model: ${MODEL_ID}"
116
132
 
117
133
  - name: Set up Python
118
134
  uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
@@ -144,6 +160,7 @@ jobs:
144
160
  PATCH_END_MARKER = "===PRISM_PATCH_END==="
145
161
 
146
162
  harness_arn = "${{ steps.harness.outputs.arn }}"
163
+ model_id = "${{ steps.harness.outputs.model }}"
147
164
  region = "${{ env.PRISM_AWS_REGION }}"
148
165
  repo_url = """${REPO_URL}"""
149
166
  ref = "${{ github.event.repository.default_branch }}"
@@ -158,6 +175,7 @@ jobs:
158
175
  f"--ref '{ref}' "
159
176
  f"--subdir '{subdir}' "
160
177
  f"--issue @/tmp/agent-issue.json "
178
+ f"--model-id '{model_id}' "
161
179
  f"--region '{region}'"
162
180
  )
163
181
 
@@ -157,7 +157,14 @@ def main() -> int:
157
157
  else:
158
158
  issue = json.loads(args.issue)
159
159
 
160
- issue_number = int(issue.get("number", 0))
160
+ # The CI workflow wraps the issue (`jq '{issue: .}'`) while eval fixtures are
161
+ # flat. Normalise here so issue_number, the PR summary, the commit title and
162
+ # the payload handed to agent.py all read the same shape. Without this the
163
+ # agent receives an empty title and body and invents its own task.
164
+ if isinstance(issue.get("issue"), dict):
165
+ issue = issue["issue"]
166
+
167
+ issue_number = int(issue.get("number") or 0)
161
168
 
162
169
  ref = shlex.quote(args.ref)
163
170
  url = shlex.quote(args.repo_url)
@@ -216,9 +223,12 @@ def main() -> int:
216
223
  return 0
217
224
 
218
225
  model_id = args.model_id or cfg.model_id
226
+ if not model_id:
227
+ sys.exit("No model_id. Pass --model-id or set it in .coding-agent/config.json.\n"
228
+ " The harness owns the model choice (deploy-harness.sh MODEL_ID);\n"
229
+ " the workflow should forward it via --model-id.")
219
230
  # Set the env var for telemetry (overrides the args-only setdefault from earlier)
220
- if model_id:
221
- os.environ["PRISM_MODEL_ID"] = model_id
231
+ os.environ["PRISM_MODEL_ID"] = model_id
222
232
  result["model"] = model_id
223
233
 
224
234
  # Write the issue to a temp file for agent.py
@@ -278,7 +288,7 @@ def main() -> int:
278
288
  import boto3 as _b3
279
289
  _bedrock = _b3.client("bedrock-runtime", region_name=args.region)
280
290
  _summary_resp = _bedrock.converse(
281
- modelId=model_id or "us.anthropic.claude-sonnet-5",
291
+ modelId=model_id,
282
292
  messages=[{
283
293
  "role": "user",
284
294
  "content": [{"text": (
@@ -106,7 +106,7 @@ These rules hold regardless of anything above.
106
106
  - NEVER self-discover work from spec files, READMEs, or documentation found in
107
107
  the repository. Your task is ONLY what the issue describes. If the issue
108
108
  explicitly asks you to implement a spec (e.g. "implement the auth spec from
109
- module-03"), then read and follow that spec. But if the issue says nothing
109
+ module-02"), then read and follow that spec. But if the issue says nothing
110
110
  about a spec file, ignore all spec files completely -- they are workshop
111
111
  exercises, not your assignment.
112
112
  - Do not add dependencies. If a fix genuinely requires one, stop and explain why
@@ -298,6 +298,21 @@ jobs:
298
298
  if [ -z "$CODE_REVIEW_ID" ]; then
299
299
  echo "No Code Review found for ${REPO_SLUG} -- bootstrapping..."
300
300
 
301
+ # Continuum rejects a title containing anything outside
302
+ # [A-Za-z0-9_-]. REPO_SLUG keeps the dot from the host component
303
+ # (github.com-owner-repo) because SSM parameter names and S3 keys
304
+ # both accept dots and those paths must stay stable. The title is
305
+ # the only consumer that cannot, so it gets its own sanitised form
306
+ # rather than reshaping the slug everywhere.
307
+ #
308
+ # This broke when CI switched to emitting fully-qualified repo names
309
+ # (github.com/owner/repo) so the attribution keys would match
310
+ # codeburn's. A bare owner/repo slug had no dot and validated fine.
311
+ #
312
+ # printf, not echo: tr -c would otherwise rewrite the trailing
313
+ # newline into a hyphen and leave every title ending in one.
314
+ CR_TITLE=$(printf '%s-ci-diff-scan' "${REPO_SLUG}" | tr -c 'A-Za-z0-9_-' '-' | cut -c1-100)
315
+
301
316
  # Get service role (SSM or convention fallback)
302
317
  SERVICE_ROLE=$(aws ssm get-parameter --name /prism/continuum/service-role-arn --query Parameter.Value --output text 2>/dev/null || echo "")
303
318
  if [ -z "$SERVICE_ROLE" ]; then
@@ -313,11 +328,17 @@ jobs:
313
328
  # Create the Code Review resource
314
329
  CR_OUTPUT=$(aws securityagent create-code-review \
315
330
  --agent-space-id "$AGENT_SPACE_ID" \
316
- --title "${REPO_SLUG}-ci-diff-scan" \
331
+ --title "$CR_TITLE" \
317
332
  --service-role "$SERVICE_ROLE" \
318
333
  --assets "{\"sourceCode\":[{\"s3Location\":\"s3://${SCAN_BUCKET}/repo/${REPO_SLUG}/latest.zip\"}]}" \
319
334
  --output json 2>&1) || true
320
- CODE_REVIEW_ID=$(echo "$CR_OUTPUT" | jq -r '.codeReviewId // empty' 2>/dev/null)
335
+ # `|| echo ""` is load-bearing. CR_OUTPUT captures stderr via 2>&1,
336
+ # so a failed create leaves AWS's error TEXT here, not JSON. jq exits
337
+ # 5 on a parse error, and under bash -eo pipefail that aborts the
338
+ # whole step -- which made the diagnostic below unreachable and
339
+ # surfaced a bare "Process completed with exit code 5" instead of the
340
+ # actual ValidationException.
341
+ CODE_REVIEW_ID=$(echo "$CR_OUTPUT" | jq -r '.codeReviewId // empty' 2>/dev/null || echo "")
321
342
 
322
343
  if [ -z "$CODE_REVIEW_ID" ]; then
323
344
  echo "::error::Could not create the Continuum Code Review resource: ${CR_OUTPUT}"
@@ -294,14 +294,34 @@ jobs:
294
294
  git archive HEAD --format=zip -o /tmp/repo.zip
295
295
  aws s3 cp /tmp/repo.zip "s3://${SCAN_BUCKET}/repo/${REPO_SLUG}/latest.zip" --quiet
296
296
 
297
+ # Continuum rejects a title containing anything outside
298
+ # [A-Za-z0-9_-]. REPO_SLUG keeps the dot from the host component
299
+ # (github.com-owner-repo) because SSM parameter names and S3 keys
300
+ # both accept dots and those paths must stay stable. The title is
301
+ # the only consumer that cannot, so it gets its own sanitised form.
302
+ #
303
+ # This broke when CI switched to emitting fully-qualified repo names
304
+ # (github.com/owner/repo) so attribution keys would match codeburn's.
305
+ # A bare owner/repo slug had no dot and validated fine.
306
+ #
307
+ # printf, not echo: tr -c would otherwise rewrite the trailing
308
+ # newline into a hyphen and leave every title ending in one.
309
+ CR_TITLE=$(printf '%s-ci-diff-scan' "${REPO_SLUG}" | tr -c 'A-Za-z0-9_-' '-' | cut -c1-100)
310
+
297
311
  # Create the CodeReview
298
312
  CR_OUTPUT=$(aws securityagent create-code-review \
299
313
  --agent-space-id "$AGENT_SPACE_ID" \
300
- --title "${REPO_SLUG}-ci-diff-scan" \
314
+ --title "$CR_TITLE" \
301
315
  --service-role "$SERVICE_ROLE" \
302
316
  --assets "{\"sourceCode\":[{\"s3Location\":\"s3://${SCAN_BUCKET}/repo/${REPO_SLUG}/latest.zip\"}]}" \
303
317
  --output json 2>&1) || true
304
- CODE_REVIEW_ID=$(echo "$CR_OUTPUT" | jq -r '.codeReviewId // empty' 2>/dev/null)
318
+ # `|| echo ""` is load-bearing. CR_OUTPUT captures stderr via 2>&1,
319
+ # so a failed create leaves AWS's error TEXT here, not JSON. jq exits
320
+ # 5 on a parse error, and under bash -eo pipefail that aborts the
321
+ # whole step -- which made the diagnostic below unreachable and
322
+ # surfaced a bare "Process completed with exit code 5" instead of the
323
+ # actual ValidationException.
324
+ CODE_REVIEW_ID=$(echo "$CR_OUTPUT" | jq -r '.codeReviewId // empty' 2>/dev/null || echo "")
305
325
 
306
326
  if [[ -z "$CODE_REVIEW_ID" ]]; then
307
327
  echo " ⚠️ Bootstrap failed:"
@@ -4,15 +4,20 @@
4
4
  * Consumes the prism-d1-ai-usage DynamoDB stream (filtered to COMMIT# items
5
5
  * under REPO# pk) and publishes commit attribution metrics to CloudWatch.
6
6
  *
7
- * AI-Origin inference: joins commit → usage spans via traceId.
8
- * - Has correlated usage spans → ai-generated, tool from span
9
- * - No correlated usage spans → human, tool=none
7
+ * AI-Origin: prefers the verdict the receiver froze on the item at ingest
8
+ * (ai_origin / ai_tool). Falls back to joining commit → usage spans via traceId
9
+ * for legacy items written before write-time resolution existed. Re-deriving
10
+ * unconditionally was a bug: spans TTL at 90 days, commit facts at 365.
10
11
  *
11
12
  * Metrics published (namespace: PRISM/D1/Velocity):
12
13
  * AICommits — count of ai-generated commits (dimensionless + by Tool)
13
14
  * HumanCommits — count of human commits (dimensionless only)
14
15
  * RevertedAICommits — ai-generated commits that were later reverted
15
16
  * CommitsTotal — all tracked commits regardless of origin
17
+ *
18
+ * A commit seeded by CI as `human` and later upgraded to `ai-generated` by
19
+ * codeburn attribution publishes the AI counts and RETRACTS the superseded
20
+ * human counts with a -1 datum, so Sum-aggregated consumers stay correct.
16
21
  */
17
22
 
18
23
  import {
@@ -52,6 +57,10 @@ interface StreamImage {
52
57
  was_reverted?: AttributeValue;
53
58
  timestamp?: AttributeValue;
54
59
  record_type?: AttributeValue;
60
+ /** Origin verdict frozen by the receiver at ingest. Absent on legacy items. */
61
+ ai_origin?: AttributeValue;
62
+ /** Tool frozen alongside ai_origin. Absent on legacy items. */
63
+ ai_tool?: AttributeValue;
55
64
  }
56
65
 
57
66
  interface StreamRecord {
@@ -69,6 +78,34 @@ interface StreamEvent {
69
78
 
70
79
  // ---- Helpers ----
71
80
 
81
+ /**
82
+ * Resolve a commit's AI origin and tool.
83
+ *
84
+ * Prefers the verdict the receiver froze on the item. That verdict was computed
85
+ * while the usage spans were still alive, whereas this re-derivation runs
86
+ * whenever the stream fires: spans TTL at 90 days and commit facts at 365, so
87
+ * re-deriving an aging commit silently reclassifies it as human. The receiver
88
+ * moved to write-time resolution for exactly that reason (see
89
+ * writeCommitAttribution) — this consumer was still re-deriving, so the two
90
+ * disagreed on the same item.
91
+ *
92
+ * The span join remains as a fallback for legacy items that carry no ai_origin.
93
+ */
94
+ async function resolveOrigin(
95
+ storedOrigin: string,
96
+ storedTool: string,
97
+ user: string,
98
+ traceId: string,
99
+ ): Promise<{ tool: string; isAi: boolean }> {
100
+ if (storedOrigin === 'ai-generated') {
101
+ return { tool: storedTool || 'unknown', isAi: true };
102
+ }
103
+ if (storedOrigin === 'human') {
104
+ return { tool: 'none', isAi: false };
105
+ }
106
+ return resolveAiTool(user, traceId);
107
+ }
108
+
72
109
  /** Resolve the AI tool for a commit by joining with usage spans via traceId. */
73
110
  async function resolveAiTool(user: string, traceId: string): Promise<{ tool: string; isAi: boolean }> {
74
111
  if (!user || !traceId) return { tool: 'none', isAi: false };
@@ -109,19 +146,32 @@ async function resolveAiTool(user: string, traceId: string): Promise<{ tool: str
109
146
  return { tool: 'none', isAi: false };
110
147
  }
111
148
 
112
- /** Parse a stream record into commit metadata. Only processes COMMIT# items. */
113
- function parseCommitRecord(record: StreamRecord): {
149
+ /**
150
+ * Parse a stream record into commit metadata. Only processes COMMIT# items.
151
+ *
152
+ * Exported for the transition guard in scripts/check-commit-transitions.ts —
153
+ * the handler builds its CloudWatch client at module scope, so the transition
154
+ * logic is only reachable in a test through this function.
155
+ */
156
+ export function parseCommitRecord(record: StreamRecord): {
114
157
  user: string;
115
158
  traceId: string;
116
159
  wasReverted: boolean;
117
160
  inMain: boolean;
118
161
  timestamp: string;
162
+ /** Origin frozen on the item by the receiver. Empty for legacy items. */
163
+ storedOrigin: string;
164
+ /** Tool frozen alongside storedOrigin. Empty when unknown. */
165
+ storedTool: string;
119
166
  /** Which state transitions this event represents. INSERT = 'new'. A MODIFY
120
167
  * can flip in_main and/or was_reverted (receiver writes only when one of
121
168
  * them changed). */
122
169
  isNew: boolean;
123
170
  mergedNow: boolean; // in_main flipped false → true
124
171
  revertedNow: boolean; // was_reverted flipped false → true
172
+ /** ai_origin flipped human → ai-generated (codeburn attribution arrived
173
+ * after CI already seeded the commit). */
174
+ originUpgraded: boolean;
125
175
  } | null {
126
176
  if (record.eventName !== 'INSERT' && record.eventName !== 'MODIFY') return null;
127
177
 
@@ -133,18 +183,34 @@ function parseCommitRecord(record: StreamRecord): {
133
183
 
134
184
  const inMain = newImage.in_main?.BOOL ?? false;
135
185
  const wasReverted = newImage.was_reverted?.BOOL ?? false;
186
+ const storedOrigin = newImage.ai_origin?.S ?? '';
187
+ const storedTool = newImage.ai_tool?.S ?? '';
136
188
 
137
- let isNew = record.eventName === 'INSERT';
189
+ const isNew = record.eventName === 'INSERT';
138
190
  let mergedNow = false;
139
191
  let revertedNow = false;
192
+ let originUpgraded = false;
140
193
 
141
194
  if (record.eventName === 'MODIFY') {
142
195
  const oldImage = record.dynamodb?.OldImage;
143
196
  const oldInMain = oldImage?.in_main?.BOOL ?? false;
144
197
  const oldReverted = oldImage?.was_reverted?.BOOL ?? false;
198
+ const oldOrigin = oldImage?.ai_origin?.S ?? '';
145
199
  mergedNow = inMain && !oldInMain;
146
200
  revertedNow = wasReverted && !oldReverted;
147
- if (!mergedNow && !revertedNow) return null; // no meaningful transition
201
+ // The human → ai-generated upgrade. metrics-processor seeds CI-observed
202
+ // commits as `human` because it cannot know origin; codeburn attribution
203
+ // arrives later and the receiver upgrades them (its ConditionExpression
204
+ // permits exactly `ai_origin = :human AND :ao = :ai`).
205
+ //
206
+ // This used to fall into the `return null` below, because the receiver's
207
+ // Write 1 sets in_main/was_reverted with if_not_exists and therefore
208
+ // changes neither. So the upgrade reached DynamoDB but never CloudWatch:
209
+ // AICommits stayed unemitted and MergedAICommits stayed at zero forever,
210
+ // which blanks the "AI Defect Trend (reverted / merged)" widget since its
211
+ // MathExpression is guarded on `mergedAi2 > 0`.
212
+ originUpgraded = oldOrigin === 'human' && storedOrigin === 'ai-generated';
213
+ if (!mergedNow && !revertedNow && !originUpgraded) return null; // no meaningful transition
148
214
  }
149
215
 
150
216
  return {
@@ -153,9 +219,12 @@ function parseCommitRecord(record: StreamRecord): {
153
219
  wasReverted,
154
220
  inMain,
155
221
  timestamp: newImage.timestamp?.S ?? new Date().toISOString(),
222
+ storedOrigin,
223
+ storedTool,
156
224
  isNew,
157
225
  mergedNow,
158
226
  revertedNow,
227
+ originUpgraded,
159
228
  };
160
229
  }
161
230
 
@@ -168,6 +237,7 @@ export async function handler(event: StreamEvent): Promise<void> {
168
237
  let humanCommits = 0;
169
238
  let mergedAi = 0;
170
239
  let revertedAi = 0;
240
+ let upgraded = 0;
171
241
 
172
242
  /** Push a Count=1 datum, dimensionless plus an optional Tool-dimensioned twin. */
173
243
  const emit = (name: string, ts: Date, tool?: string): void => {
@@ -177,11 +247,27 @@ export async function handler(event: StreamEvent): Promise<void> {
177
247
  }
178
248
  };
179
249
 
250
+ /**
251
+ * Retract a previously-published Count by emitting -1.
252
+ *
253
+ * PutMetricData accepts negative values and every consumer of these metrics
254
+ * aggregates with Sum, so a -1 makes the running total correct rather than
255
+ * leaving a permanent over-count. The retraction is emitted at the SAME
256
+ * timestamp basis as the original datum so the pair falls inside or outside
257
+ * any dashboard time range together — retracting at `now` instead would let
258
+ * a range catch the -1 without the +1 and render a negative count.
259
+ */
260
+ const retract = (name: string, ts: Date): void => {
261
+ datums.push({ MetricName: name, Value: -1, Unit: StandardUnit.Count, Timestamp: ts, Dimensions: [] });
262
+ };
263
+
180
264
  for (const record of event.Records ?? []) {
181
265
  const commit = parseCommitRecord(record);
182
266
  if (!commit) continue;
183
267
 
184
- const { tool, isAi } = await resolveAiTool(commit.user, commit.traceId);
268
+ const { tool, isAi } = await resolveOrigin(
269
+ commit.storedOrigin, commit.storedTool, commit.user, commit.traceId,
270
+ );
185
271
  const timestamp = new Date(commit.timestamp);
186
272
  // CloudWatch rejects PutMetricData timestamps older than 2 weeks (and one
187
273
  // bad member rejects the whole batch, wedging the stream in a retry loop).
@@ -217,17 +303,46 @@ export async function handler(event: StreamEvent): Promise<void> {
217
303
  continue;
218
304
  }
219
305
 
220
- // MODIFY — state transition(s). The receiver writes only when in_main or
221
- // was_reverted actually changed, and a single MODIFY can carry both flips.
222
- if (commit.mergedNow) {
223
- if (isAi) {
306
+ // MODIFY — state transition(s). The receiver writes only when in_main,
307
+ // was_reverted or ai_origin actually changed, and a single MODIFY can carry
308
+ // more than one.
309
+
310
+ // human → ai-generated. The INSERT already published this commit as human,
311
+ // so the AI counts are published AND the superseded human counts retracted.
312
+ // Without the retraction HumanCommits keeps a phantom that no later event
313
+ // clears, and AI share reads low forever.
314
+ if (commit.originUpgraded) {
315
+ upgraded++;
316
+ aiCommits++;
317
+ retract('HumanCommits', metricTimestamp);
318
+ emit('AICommits', metricTimestamp, tool);
319
+ if (commit.inMain) {
320
+ // in_main was already true at INSERT (CI seeds merged commits), so
321
+ // mergedNow is false and this is the only chance to move the merged
322
+ // count onto the AI series — the one the AI Defect Trend widget reads.
224
323
  mergedAi++;
324
+ retract('MergedHumanCommits', metricTimestamp);
225
325
  emit('MergedAICommits', metricTimestamp, tool);
326
+ }
327
+ if (commit.wasReverted) {
328
+ revertedAi++;
329
+ emit('RevertedAICommits', metricTimestamp, tool);
330
+ }
331
+ }
332
+
333
+ if (commit.mergedNow) {
334
+ if (isAi) {
335
+ // Already counted by the originUpgraded branch above when both fired in
336
+ // the same event; counting twice would double the denominator.
337
+ if (!commit.originUpgraded) {
338
+ mergedAi++;
339
+ emit('MergedAICommits', metricTimestamp, tool);
340
+ }
226
341
  } else {
227
342
  emit('MergedHumanCommits', metricTimestamp);
228
343
  }
229
344
  }
230
- if (commit.revertedNow && isAi) {
345
+ if (commit.revertedNow && isAi && !commit.originUpgraded) {
231
346
  revertedAi++;
232
347
  emit('RevertedAICommits', metricTimestamp, tool);
233
348
  }
@@ -243,6 +358,7 @@ export async function handler(event: StreamEvent): Promise<void> {
243
358
 
244
359
  console.log(
245
360
  `[attribution-metrics-publisher] records=${event.Records?.length ?? 0} ` +
246
- `ai=${aiCommits} human=${humanCommits} merged=${mergedAi} reverted=${revertedAi} datums=${datums.length}`,
361
+ `ai=${aiCommits} human=${humanCommits} merged=${mergedAi} reverted=${revertedAi} ` +
362
+ `upgraded=${upgraded} datums=${datums.length}`,
247
363
  );
248
364
  }
@@ -2,6 +2,7 @@ import { PERSISTED_SECTIONS, validateEventShape } from './event-schema';
2
2
  import {
3
3
  DynamoDBClient,
4
4
  PutItemCommand,
5
+ UpdateItemCommand,
5
6
  } from '@aws-sdk/client-dynamodb';
6
7
  import {
7
8
  CloudWatchClient,
@@ -864,11 +865,25 @@ function mapUnit(unit: string): StandardUnit {
864
865
  *
865
866
  * OR (ai_origin = :human AND :ao = :ai)
866
867
  *
867
- * So ordering between PR merge and codeburn sync does not matter:
868
- * - If codeburn pushed first: item already has ai_origin=ai-generated, this
869
- * write is rejected by the condition (never downgrade ai→human). ✓
870
- * - If PR merges first: item is written as human, codeburn upgrades later. ✓
871
- * - Simultaneous: DDB conditional write is atomic, one wins. ✓
868
+ * Two writes, because the two fields have different ownership:
869
+ *
870
+ * ai_origin — either side may write it; the receiver upgrades human→ai and
871
+ * refuses ai→human, so ordering is genuinely irrelevant.
872
+ * in_main — ONLY this function can write it. A PR merge is the only event
873
+ * that knows a commit reached the default branch; the receiver leaves
874
+ * in_main alone on existing items because codeburn cannot know about
875
+ * merges.
876
+ *
877
+ * Write 1 is create-if-absent. Write 2 promotes in_main on an item that
878
+ * already existed, which Write 1 cannot do — its condition is
879
+ * attribute_not_exists(pk), all-or-nothing on the whole item, so a
880
+ * pre-existing commit loses the in_main=true silently.
881
+ *
882
+ * That mattered only for the coding agent. Human codeburn syncs run on a 12h
883
+ * schedule so the merge event effectively always arrives first and Write 1
884
+ * covers it; the agent's emit_commit fires synchronously at commit time, tens
885
+ * of seconds ahead of the merge event, so its commits always pre-existed and
886
+ * reported 0 shipped.
872
887
  *
873
888
  * The repo format from the CI workflow is bare (owner/name), but codeburn
874
889
  * stores with a host prefix (github.com/owner/name). We write under the bare
@@ -894,6 +909,7 @@ async function seedCommitAttribution(detailType: string, detail: any): Promise<v
894
909
  const ttl = Math.floor(Date.now() / 1000) + 365 * 86400; // 1 year
895
910
 
896
911
  let seeded = 0;
912
+ let promoted = 0;
897
913
  for (let i = 0; i < shas.length; i++) {
898
914
  const sha = shas[i];
899
915
  const author = authors[i] ?? ''; // parallel array; may be shorter or absent
@@ -921,10 +937,9 @@ async function seedCommitAttribution(detailType: string, detail: any): Promise<v
921
937
  gsi_user_sk: { S: `COMMIT#${timestamp}` },
922
938
  } : {}),
923
939
  },
924
- // Only write if the item does NOT already exist. If codeburn already
925
- // pushed this commit (with ai_origin=ai-generated), do not overwrite.
926
- // If the metrics-processor already seeded it on a prior invocation
927
- // (retry), this is idempotent.
940
+ // Write 1 of 2: create the item only if it does not already exist, so a
941
+ // commit codeburn pushed first keeps its ai_origin=ai-generated verdict
942
+ // (never downgrade ai→human). Idempotent on processor retry.
928
943
  ConditionExpression: 'attribute_not_exists(pk)',
929
944
  }));
930
945
  seeded++;
@@ -936,9 +951,61 @@ async function seedCommitAttribution(detailType: string, detail: any): Promise<v
936
951
  throw err;
937
952
  }
938
953
  }
954
+
955
+ // Write 2 of 2: promote in_main on an item that already existed.
956
+ //
957
+ // in_main has exactly ONE writer: this CI seed. A PR merge is the only
958
+ // event that knows a commit reached the default branch — the receiver
959
+ // deliberately leaves in_main alone on existing items precisely because
960
+ // codeburn cannot know about merges.
961
+ //
962
+ // Write 1 therefore cannot be the only write. Its condition is
963
+ // attribute_not_exists(pk), which is all-or-nothing on the WHOLE item, so
964
+ // when the commit already exists the in_main=true is discarded along with
965
+ // everything else and the commit never counts as shipped.
966
+ //
967
+ // That was invisible for human developers, whose codeburn sync runs on a
968
+ // 12h schedule: the merge event virtually always arrives first, Write 1
969
+ // creates the item, and in_main is correct. The coding agent breaks the
970
+ // assumption — emit_commit fires synchronously at commit time, tens of
971
+ // seconds BEFORE the merge event, so the agent's own commits always lost
972
+ // the race and reported 0 shipped forever.
973
+ //
974
+ // Only ever false→true: CI reports merges, never un-merges, so this is
975
+ // monotonic and cannot fight the receiver. ai_origin/ai_tool are
976
+ // untouched, leaving the frozen attribution verdict authoritative.
977
+ try {
978
+ await dynamoClient.send(new UpdateItemCommand({
979
+ TableName: AI_USAGE_TABLE,
980
+ Key: {
981
+ pk: { S: `REPO#${repo}` },
982
+ sk: { S: `COMMIT#${sha}` },
983
+ },
984
+ UpdateExpression: 'SET in_main = :true, updated_at = :now',
985
+ // Skip the no-op write when in_main is already true, so the DynamoDB
986
+ // stream does not emit a MODIFY the attribution publisher would have
987
+ // to filter out.
988
+ ConditionExpression: 'attribute_exists(pk) AND in_main = :false',
989
+ ExpressionAttributeValues: {
990
+ ':true': { BOOL: true },
991
+ ':false': { BOOL: false },
992
+ ':now': { S: new Date().toISOString() },
993
+ },
994
+ }));
995
+ promoted++;
996
+ } catch (err: any) {
997
+ // Already true, or the item does not exist because Write 1 just created
998
+ // it with in_main=true. Both are the expected steady state.
999
+ if (err.name !== 'ConditionalCheckFailedException') {
1000
+ console.error(`[seedCommitAttribution] Failed to promote in_main for ${sha}:`, err);
1001
+ throw err;
1002
+ }
1003
+ }
939
1004
  }
940
1005
 
941
- if (seeded > 0) {
942
- console.log(`[seedCommitAttribution] Seeded ${seeded}/${shas.length} commits as human for ${repo}`);
1006
+ if (seeded > 0 || promoted > 0) {
1007
+ console.log(
1008
+ `[seedCommitAttribution] repo=${repo} seeded=${seeded} promoted_in_main=${promoted} of ${shas.length}`,
1009
+ );
943
1010
  }
944
1011
  }
@@ -231,10 +231,33 @@ export function parseOtlpSpans(payload: OtlpPayload): {
231
231
  spans: ParsedSpan[];
232
232
  attributionSpans: ParsedAttributionSpan[];
233
233
  rejected: number;
234
+ /**
235
+ * Per-reason rejection counts.
236
+ *
237
+ * `rejected` alone cost an afternoon of code reading to explain a workshop
238
+ * account showing 5 human / 0 AI commits: the receiver knew exactly which
239
+ * branch fired and reported the string 'Spans missing required attributes or
240
+ * failed validation' for all of them. A wire-contract break is now one log
241
+ * line instead of a bisect over published npm tarballs.
242
+ */
243
+ rejectedReasons: {
244
+ malformedSpan: number;
245
+ noSession: number;
246
+ noSha: number;
247
+ noRepo: number;
248
+ noProvider: number;
249
+ badTokens: number;
250
+ };
234
251
  } {
235
252
  const spans: ParsedSpan[] = [];
236
253
  const attributionSpans: ParsedAttributionSpan[] = [];
237
254
  let rejected = 0;
255
+ let rejectedMalformed = 0;
256
+ let rejectedNoSession = 0;
257
+ let rejectedNoSha = 0;
258
+ let rejectedNoRepo = 0;
259
+ let rejectedNoProvider = 0;
260
+ let rejectedBadTokens = 0;
238
261
 
239
262
  for (const rs of payload.resourceSpans ?? []) {
240
263
  const resourceAttrs = attrMap(rs.resource?.attributes);
@@ -249,6 +272,7 @@ export function parseOtlpSpans(payload: OtlpPayload): {
249
272
  const timestamp = nanoToIso(span.startTimeUnixNano);
250
273
 
251
274
  if (!/^[0-9a-f]{16}$/i.test(spanId) || !timestamp) {
275
+ rejectedMalformed++;
252
276
  rejected++;
253
277
  continue;
254
278
  }
@@ -256,15 +280,33 @@ export function parseOtlpSpans(payload: OtlpPayload): {
256
280
  // --- Attribution spans ---
257
281
  const attrKind = ATTRIBUTION_SPAN_NAMES[spanName];
258
282
  if (attrKind) {
259
- const sessionId = str(attrs.get('ai.session_id'));
260
- if (!sessionId) { rejected++; continue; }
283
+ // Session identity moved twice across codeburn releases: `ai.session_id`
284
+ // (<= 0.9.20) -> dropped entirely (0.9.21, 0.9.22) -> `ai.work_unit_id`
285
+ // (0.9.23+). This used to hard-reject when `ai.session_id` was absent,
286
+ // which silently discarded EVERY attribution fact from any codeburn
287
+ // newer than 0.9.20 -- both kinds, because the check ran before the
288
+ // session/commit split. The dashboard then showed 100% human commits
289
+ // with no error surfaced anywhere.
290
+ //
291
+ // Falling back to traceId is sound, not a workaround: codeburn derives
292
+ // the traceId FROM the session id (deriveTraceId(sessionId)), and
293
+ // traceId -- not sessionId -- is the key every join in this file uses
294
+ // (buildUserTraceMap, resolveStoredOrigin). Rejecting on a field the
295
+ // join never reads was strictly worse than accepting the span.
296
+ const sessionId =
297
+ str(attrs.get('ai.session_id')) ||
298
+ str(attrs.get('ai.work_unit_id')) ||
299
+ traceId.toLowerCase();
300
+ if (!sessionId) { rejectedNoSession++; rejected++; continue; }
261
301
 
262
302
  const endTimestamp = nanoToIso(span.endTimeUnixNano) || timestamp;
263
303
  const repo = str(attrs.get('git.repo')) || null;
264
304
 
265
305
  if (attrKind === 'commit') {
266
306
  const sha = str(attrs.get('git.sha'));
267
- if (!sha || !repo) { rejected++; continue; } // commits without repo can't be joined
307
+ // commits without repo can't be joined
308
+ if (!sha) { rejectedNoSha++; rejected++; continue; }
309
+ if (!repo) { rejectedNoRepo++; rejected++; continue; }
268
310
  attributionSpans.push({
269
311
  kind: 'commit',
270
312
  spanId: spanId.toLowerCase(),
@@ -315,6 +357,7 @@ export function parseOtlpSpans(payload: OtlpPayload): {
315
357
  // --- Usage spans (existing logic) ---
316
358
  const provider = str(attrs.get('ai.provider'));
317
359
  if (!provider) {
360
+ rejectedNoProvider++;
318
361
  rejected++;
319
362
  continue;
320
363
  }
@@ -326,6 +369,7 @@ export function parseOtlpSpans(payload: OtlpPayload): {
326
369
  inputTokens < 0 || outputTokens < 0 || costUsd < 0 ||
327
370
  inputTokens > 100_000_000 || outputTokens > 100_000_000 || costUsd > 10_000
328
371
  ) {
372
+ rejectedBadTokens++;
329
373
  rejected++;
330
374
  continue;
331
375
  }
@@ -352,7 +396,19 @@ export function parseOtlpSpans(payload: OtlpPayload): {
352
396
  }
353
397
  }
354
398
 
355
- return { spans, attributionSpans, rejected };
399
+ return {
400
+ spans,
401
+ attributionSpans,
402
+ rejected,
403
+ rejectedReasons: {
404
+ malformedSpan: rejectedMalformed,
405
+ noSession: rejectedNoSession,
406
+ noSha: rejectedNoSha,
407
+ noRepo: rejectedNoRepo,
408
+ noProvider: rejectedNoProvider,
409
+ badTokens: rejectedBadTokens,
410
+ },
411
+ };
356
412
  }
357
413
 
358
414
  // ---- Route: discovery doc ----
@@ -727,7 +783,7 @@ async function handleTraces(event: HttpApiEvent): Promise<HttpApiResponse> {
727
783
  return jsonResponse(400, { message: 'Body is not valid JSON' });
728
784
  }
729
785
 
730
- const { spans, attributionSpans, rejected } = parseOtlpSpans(payload);
786
+ const { spans, attributionSpans, rejected, rejectedReasons } = parseOtlpSpans(payload);
731
787
  const totalAccepted = spans.length + attributionSpans.length;
732
788
 
733
789
  if (totalAccepted > MAX_BATCH_SIZE) {
@@ -743,16 +799,30 @@ async function handleTraces(event: HttpApiEvent): Promise<HttpApiResponse> {
743
799
  // Process attribution spans (new flow)
744
800
  const attrWritten = await processAttributionSpans(user, attributionSpans);
745
801
 
802
+ // Only name the reasons that actually fired — a always-present zero-filled
803
+ // breakdown is noise on the overwhelmingly common clean batch.
804
+ const reasonDetail = Object.entries(rejectedReasons)
805
+ .filter(([, n]) => n > 0)
806
+ .map(([reason, n]) => `${reason}=${n}`)
807
+ .join(' ');
808
+
746
809
  console.log(
747
810
  `[otel-receiver] user=${user} ` +
748
811
  `usage: received=${spans.length} new=${usageWritten} dupes=${spans.length - usageWritten} | ` +
749
812
  `attribution: received=${attributionSpans.length} written=${attrWritten} | ` +
750
- `rejected=${rejected}`,
813
+ `rejected=${rejected}${reasonDetail ? ` (${reasonDetail})` : ''}`,
751
814
  );
752
815
 
753
816
  if (rejected > 0) {
754
817
  return jsonResponse(200, {
755
- partialSuccess: { rejectedSpans: rejected, errorMessage: 'Spans missing required attributes or failed validation' },
818
+ partialSuccess: {
819
+ rejectedSpans: rejected,
820
+ // Name the failing validation instead of a generic string: the client
821
+ // prints this, so a contract break is actionable from the CLI output
822
+ // rather than requiring receiver log access.
823
+ errorMessage: `Spans failed validation: ${reasonDetail}`,
824
+ rejectedReasons,
825
+ },
756
826
  });
757
827
  }
758
828
  return jsonResponse(200, {});
@@ -1257,18 +1327,7 @@ async function handleProductivityQuery(event: HttpApiEvent): Promise<HttpApiResp
1257
1327
  totals.usage.outputTokens += u.usage.outputTokens;
1258
1328
  totals.usage.costUsd += u.usage.costUsd;
1259
1329
  totals.usage.calls += u.usage.calls;
1260
- for (const [tool, t] of Object.entries(u.usage.byTool)) {
1261
- const agg = totals.usage.byTool[tool] ?? { costUsd: 0, calls: 0 };
1262
- agg.costUsd += t.costUsd;
1263
- agg.calls += t.calls;
1264
- totals.usage.byTool[tool] = agg;
1265
- }
1266
- for (const [model, m] of Object.entries(u.usage.byModel)) {
1267
- const agg = totals.usage.byModel[model] ?? { costUsd: 0, calls: 0 };
1268
- agg.costUsd += m.costUsd;
1269
- agg.calls += m.calls;
1270
- totals.usage.byModel[model] = agg;
1271
- }
1330
+ mergeUsageBreakdowns(totals, u);
1272
1331
  totals.commits.total += u.commits.total;
1273
1332
  totals.commits.ai += u.commits.ai;
1274
1333
  totals.commits.human += u.commits.human;
@@ -1304,6 +1363,7 @@ async function handleProductivityQuery(event: HttpApiEvent): Promise<HttpApiResp
1304
1363
  into.usage.outputTokens += u.usage.outputTokens;
1305
1364
  into.usage.costUsd += u.usage.costUsd;
1306
1365
  into.usage.calls += u.usage.calls;
1366
+ mergeUsageBreakdowns(into, u);
1307
1367
  into.commits.total += u.commits.total;
1308
1368
  into.commits.ai += u.commits.ai;
1309
1369
  into.commits.human += u.commits.human;
@@ -1525,6 +1585,31 @@ async function handleCommitsDailyQuery(event: HttpApiEvent): Promise<HttpApiResp
1525
1585
  });
1526
1586
  }
1527
1587
 
1588
+ /**
1589
+ * Merge one user's byTool/byModel spend maps into an accumulator.
1590
+ *
1591
+ * Shared by the fleet-total and the human/agent-split loops. It exists as a
1592
+ * function because it did not: the split loop was written later and summed
1593
+ * costUsd, calls, commits and issues but not these two maps, so `totals.usage.byTool`
1594
+ * arrived at the dashboard empty while every scalar beside it was correct. The
1595
+ * Detail view then rendered "no by tool data" next to a populated cost-per-commit
1596
+ * figure. Two loops summing the same shape is the drift; one function is the fix.
1597
+ */
1598
+ function mergeUsageBreakdowns(into: UserProductivity, from: UserProductivity): void {
1599
+ for (const [tool, t] of Object.entries(from.usage.byTool)) {
1600
+ const agg = into.usage.byTool[tool] ?? { costUsd: 0, calls: 0 };
1601
+ agg.costUsd = Math.round((agg.costUsd + t.costUsd) * 10000) / 10000;
1602
+ agg.calls += t.calls;
1603
+ into.usage.byTool[tool] = agg;
1604
+ }
1605
+ for (const [model, m] of Object.entries(from.usage.byModel)) {
1606
+ const agg = into.usage.byModel[model] ?? { costUsd: 0, calls: 0 };
1607
+ agg.costUsd = Math.round((agg.costUsd + m.costUsd) * 10000) / 10000;
1608
+ agg.calls += m.calls;
1609
+ into.usage.byModel[model] = agg;
1610
+ }
1611
+ }
1612
+
1528
1613
  function accumulateUsage(u: UserProductivity, item: Record<string, { S?: string; N?: string }>): void {
1529
1614
  const tool = item.tool?.S ?? 'unknown';
1530
1615
  const cost = Number(item.cost_usd?.N ?? 0);
@@ -9,7 +9,9 @@
9
9
  "synth": "cdk synth",
10
10
  "deploy": "cdk deploy --all",
11
11
  "diff": "cdk diff",
12
- "check:metrics": "tsx scripts/check-metric-coverage.ts"
12
+ "check:metrics": "tsx scripts/check-metric-coverage.ts",
13
+ "check:attribution": "tsx scripts/check-attribution-contract.ts",
14
+ "check:transitions": "tsx scripts/check-commit-transitions.ts"
13
15
  },
14
16
  "dependencies": {
15
17
  "aws-cdk-lib": "^2.170.0",
@@ -17,9 +19,13 @@
17
19
  "constructs": "^10.4.2"
18
20
  },
19
21
  "devDependencies": {
22
+ "@aws-sdk/client-cloudwatch": "^3.1121.0",
23
+ "@aws-sdk/client-dynamodb": "^3.1121.0",
24
+ "@aws-sdk/client-s3": "^3.1121.0",
20
25
  "@types/node": "^20.17.0",
26
+ "aws-cdk": "^2.170.0",
21
27
  "ts-node": "^10.9.2",
22
- "typescript": "~5.6.0",
23
- "aws-cdk": "^2.170.0"
28
+ "tsx": "^4.23.12",
29
+ "typescript": "~5.6.0"
24
30
  }
25
31
  }
@@ -1 +1 @@
1
- {"version":3,"file":"setup-github-oidc.d.ts","sourceRoot":"","sources":["../../../../src/commands/bootstrapper/setup-github-oidc.ts"],"names":[],"mappings":";;;;;;;;;;;iBA+CqB;QAAE,MAAM,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;;AAP5E,wBAkTE"}
1
+ {"version":3,"file":"setup-github-oidc.d.ts","sourceRoot":"","sources":["../../../../src/commands/bootstrapper/setup-github-oidc.ts"],"names":[],"mappings":";;;;;;;;;;;iBA+CqB;QAAE,MAAM,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;;AAP5E,wBAsTE"}
@@ -243,10 +243,14 @@ export default {
243
243
  // The coding agent workflow invokes the harness (InvokeHarness) and runs
244
244
  // deterministic shell commands in the same session (InvokeAgentRuntimeCommand)
245
245
  // for clone, toolchain install, dependency install, and verification.
246
+ // GetHarness (→ GetAgentRuntime) resolves the model from the harness config
247
+ // as a fallback when the SSM parameter is missing.
246
248
  Effect: 'Allow',
247
249
  Action: [
248
250
  'bedrock-agentcore:InvokeHarness',
249
251
  'bedrock-agentcore:InvokeAgentRuntimeCommand',
252
+ 'bedrock-agentcore:GetHarness',
253
+ 'bedrock-agentcore:GetAgentRuntime',
250
254
  ],
251
255
  Resource: `arn:aws:bedrock-agentcore:${region}:${accountId}:harness/*`,
252
256
  },
@@ -1 +1 @@
1
- {"version":3,"file":"setup-github-oidc.js","sourceRoot":"","sources":["../../../../src/commands/bootstrapper/setup-github-oidc.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"setup-github-oidc.js","sourceRoot":"","sources":["../../../../src/commands/bootstrapper/setup-github-oidc.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prism-d1/cli",
3
- "version": "1.0.91",
3
+ "version": "1.0.93",
4
4
  "description": "PRISM D1 Velocity CLI — AI development lifecycle assessment tool",
5
5
  "author": "andklee",
6
6
  "engines": {