qualia-framework 6.6.0 → 6.7.1

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.
@@ -1158,6 +1158,182 @@ else
1158
1158
  fail_case "next-report-id missing peeked: true"
1159
1159
  fi
1160
1160
 
1161
+ # ─── A5: per-increment, concurrency-aware layout ─────────
1162
+ echo ""
1163
+ echo "A5 increment layout:"
1164
+
1165
+ # 58. migrate converts a legacy project to increments + releases + gitignore + manifest
1166
+ TMP=$(make_project)
1167
+ MIG=$(cd "$TMP" && $NODE "$STATE_JS" migrate 2>&1)
1168
+ if echo "$MIG" | grep -q '"ok": true' \
1169
+ && [ -f "$TMP/.planning/increments/inc-0001-foundation.md" ] \
1170
+ && [ -f "$TMP/.planning/increments/inc-0002-core.md" ] \
1171
+ && [ -f "$TMP/.planning/releases/r1.md" ] \
1172
+ && [ -f "$TMP/.planning/.gitignore" ] \
1173
+ && [ -f "$TMP/.planning/migration-manifest.json" ] \
1174
+ && grep -q '^STATE.md$' "$TMP/.planning/.gitignore" \
1175
+ && grep -q '^tracking.json$' "$TMP/.planning/.gitignore"; then
1176
+ pass "migrate creates increments/ + releases/ + .gitignore + manifest"
1177
+ else
1178
+ fail_case "migrate layout" "out=$MIG"
1179
+ fi
1180
+
1181
+ # 59. migrate is idempotent (re-run is a no-op)
1182
+ MIG2=$(cd "$TMP" && $NODE "$STATE_JS" migrate 2>&1)
1183
+ if echo "$MIG2" | grep -q '"already_migrated": true'; then
1184
+ pass "migrate is idempotent (already_migrated on re-run)"
1185
+ else
1186
+ fail_case "migrate idempotent" "out=$MIG2"
1187
+ fi
1188
+
1189
+ # 60. migrate --revert restores STATE.md + tracking.json and removes the layout
1190
+ REV=$(cd "$TMP" && $NODE "$STATE_JS" migrate --revert 2>&1)
1191
+ if echo "$REV" | grep -q '"restored": true' \
1192
+ && [ ! -d "$TMP/.planning/increments" ] \
1193
+ && [ ! -d "$TMP/.planning/releases" ] \
1194
+ && [ ! -f "$TMP/.planning/.cursor.json" ] \
1195
+ && [ -f "$TMP/.planning/STATE.md" ] \
1196
+ && [ -f "$TMP/.planning/tracking.json" ]; then
1197
+ pass "migrate --revert restores legacy files + removes layout"
1198
+ else
1199
+ fail_case "migrate revert" "out=$REV"
1200
+ fi
1201
+
1202
+ # 61. claim writes claimed_by + branch onto the increment file
1203
+ TMP=$(make_project)
1204
+ (cd "$TMP" && $NODE "$STATE_JS" migrate >/dev/null 2>&1)
1205
+ CL=$(cd "$TMP" && QUALIA_ACTOR=alice $NODE "$STATE_JS" claim --id inc-0001-foundation --branch feat-a 2>&1)
1206
+ if echo "$CL" | grep -q '"claimed_by": "alice"' \
1207
+ && echo "$CL" | grep -q '"branch": "feat-a"' \
1208
+ && grep -q '^claimed_by: alice$' "$TMP/.planning/increments/inc-0001-foundation.md" \
1209
+ && grep -q '^branch: feat-a$' "$TMP/.planning/increments/inc-0001-foundation.md"; then
1210
+ pass "claim writes claimed_by + branch to increment file"
1211
+ else
1212
+ fail_case "claim writes fields" "out=$CL"
1213
+ fi
1214
+
1215
+ # 62. a second actor claiming a held increment is refused
1216
+ DC=$(cd "$TMP" && QUALIA_ACTOR=bob $NODE "$STATE_JS" claim --id inc-0001-foundation --branch feat-b 2>&1)
1217
+ if echo "$DC" | grep -q '"error": "ALREADY_CLAIMED"'; then
1218
+ pass "double-claim by another actor is refused (ALREADY_CLAIMED)"
1219
+ else
1220
+ fail_case "double-claim refused" "out=$DC"
1221
+ fi
1222
+
1223
+ # 63. check (increment layout) skips the increment claimed by another actor
1224
+ CK=$(cd "$TMP" && QUALIA_ACTOR=bob $NODE "$STATE_JS" check 2>&1)
1225
+ if echo "$CK" | grep -q '"layout": "increments"' \
1226
+ && echo "$CK" | grep -q '"next_increment": "inc-0002-core"' \
1227
+ && echo "$CK" | grep -q '"claimed_by": "alice"'; then
1228
+ pass "check routes around another actor's claim (next_increment skips it)"
1229
+ else
1230
+ fail_case "check skip-claimed" "out=$CK"
1231
+ fi
1232
+
1233
+ # 64. release ships the increment + clears the claim (migrated DoD is satisfied)
1234
+ RL=$(cd "$TMP" && QUALIA_ACTOR=alice $NODE "$STATE_JS" release --id inc-0001-foundation --deployed-url https://x.example 2>&1)
1235
+ if echo "$RL" | grep -q '"status": "shipped"' \
1236
+ && echo "$RL" | grep -q '"claim_cleared_from": "alice"' \
1237
+ && grep -q '^status: shipped$' "$TMP/.planning/increments/inc-0001-foundation.md" \
1238
+ && grep -q '^claimed_by: $' "$TMP/.planning/increments/inc-0001-foundation.md" \
1239
+ && grep -q '^deployed_url: https://x.example$' "$TMP/.planning/increments/inc-0001-foundation.md"; then
1240
+ pass "release ships increment + clears claim + records deployed_url"
1241
+ else
1242
+ fail_case "release ships" "out=$RL"
1243
+ fi
1244
+
1245
+ # 65. release requires --deployed-url
1246
+ RLN=$(cd "$TMP" && QUALIA_ACTOR=alice $NODE "$STATE_JS" release --id inc-0002-core 2>&1)
1247
+ if echo "$RLN" | grep -q '"error": "MISSING_ARG"'; then
1248
+ pass "release without --deployed-url is refused (MISSING_ARG)"
1249
+ else
1250
+ fail_case "release requires url" "out=$RLN"
1251
+ fi
1252
+
1253
+ # 66. DoD gate (strict): an open '- [ ]' line blocks release
1254
+ TMP=$(make_project)
1255
+ (cd "$TMP" && $NODE "$STATE_JS" migrate >/dev/null 2>&1)
1256
+ printf '\n- [ ] unfinished acceptance item\n' >> "$TMP/.planning/increments/inc-0001-foundation.md"
1257
+ DOD=$(cd "$TMP" && $NODE "$STATE_JS" release --id inc-0001-foundation --deployed-url https://x.example 2>&1)
1258
+ if echo "$DOD" | grep -q '"error": "DOD_INCOMPLETE"'; then
1259
+ pass "DoD gate (strict): open checklist item blocks release"
1260
+ else
1261
+ fail_case "DoD strict block" "out=$DOD"
1262
+ fi
1263
+
1264
+ # 67. DoD gate: --force (OWNER override) ships despite open items
1265
+ DODF=$(cd "$TMP" && $NODE "$STATE_JS" release --id inc-0001-foundation --deployed-url https://x.example --force 2>&1)
1266
+ if echo "$DODF" | grep -q '"status": "shipped"'; then
1267
+ pass "DoD gate: --force ships despite open checklist items"
1268
+ else
1269
+ fail_case "DoD force" "out=$DODF"
1270
+ fi
1271
+
1272
+ # 68. DoD gate (standard): open item allowed only with a WAIVED reason
1273
+ TMP=$(make_project)
1274
+ (cd "$TMP" && $NODE "$STATE_JS" migrate >/dev/null 2>&1)
1275
+ printf '\n- [ ] perf budget WAIVED: out of scope for v1 (ADR-014)\n' >> "$TMP/.planning/increments/inc-0001-foundation.md"
1276
+ DODS=$(cd "$TMP" && QUALIA_PROFILE=standard $NODE "$STATE_JS" release --id inc-0001-foundation --deployed-url https://x.example 2>&1)
1277
+ if echo "$DODS" | grep -q '"status": "shipped"'; then
1278
+ pass "DoD gate (standard): WAIVED open item permits release"
1279
+ else
1280
+ fail_case "DoD standard waive" "out=$DODS"
1281
+ fi
1282
+
1283
+ # 69. legacy projects (no increments/) are untouched by the new path
1284
+ TMP=$(make_project)
1285
+ LEG=$(cd "$TMP" && $NODE "$STATE_JS" check 2>&1)
1286
+ if echo "$LEG" | grep -q '"next_command": "/qualia-plan 1"' \
1287
+ && ! echo "$LEG" | grep -q '"layout": "increments"'; then
1288
+ pass "legacy project check is unchanged (no increment layout)"
1289
+ else
1290
+ fail_case "legacy untouched" "out=$LEG"
1291
+ fi
1292
+
1293
+ # 70. TWO-ACTOR KEYSTONE: two clones ship two increments → merge with zero conflict
1294
+ if command -v git >/dev/null 2>&1; then
1295
+ ORIGIN=$(mktemp -d); CA=$(mktemp -d); CB=$(mktemp -d)
1296
+ (cd "$ORIGIN" && git init -q && git config user.email o@o.com && git config user.name origin && git config commit.gpgsign false \
1297
+ && $NODE "$STATE_JS" init --project "Conc" --phases '[{"name":"Alpha","goal":"A"},{"name":"Beta","goal":"B"}]' >/dev/null 2>&1 \
1298
+ && $NODE "$STATE_JS" migrate >/dev/null 2>&1 \
1299
+ && git add -A && git commit -q -m "seed")
1300
+ DEFBR=$(cd "$ORIGIN" && git branch --show-current)
1301
+ git clone -q "$ORIGIN" "$CA" 2>/dev/null; git clone -q "$ORIGIN" "$CB" 2>/dev/null
1302
+ (cd "$CA" && git config user.email a@a.com && git config user.name alice && git config commit.gpgsign false && git checkout -b feat-a -q \
1303
+ && QUALIA_ACTOR=alice $NODE "$STATE_JS" claim --id inc-0001-alpha --branch feat-a >/dev/null 2>&1 \
1304
+ && QUALIA_ACTOR=alice $NODE "$STATE_JS" release --id inc-0001-alpha --deployed-url https://a.example >/dev/null 2>&1 \
1305
+ && git add .planning/increments/inc-0001-alpha.md && git commit -q -m "ship alpha" && git push -q origin feat-a)
1306
+ (cd "$CB" && git config user.email b@b.com && git config user.name bob && git config commit.gpgsign false && git checkout -b feat-b -q \
1307
+ && QUALIA_ACTOR=bob $NODE "$STATE_JS" claim --id inc-0002-beta --branch feat-b >/dev/null 2>&1 \
1308
+ && QUALIA_ACTOR=bob $NODE "$STATE_JS" release --id inc-0002-beta --deployed-url https://b.example >/dev/null 2>&1 \
1309
+ && git add .planning/increments/inc-0002-beta.md && git commit -q -m "ship beta" && git push -q origin feat-b)
1310
+ MRG=$(cd "$ORIGIN" && git checkout -q "$DEFBR" && git merge feat-a -m ma >/dev/null 2>&1; git merge feat-b -m mb 2>&1; echo "exit=$?")
1311
+ CONFLICTS=$(cd "$ORIGIN" && git grep -l '<<<<<<<' HEAD -- .planning 2>/dev/null | wc -l | tr -d ' ')
1312
+ STATE_TRACKED=$(cd "$ORIGIN" && git ls-files .planning/STATE.md | wc -l | tr -d ' ')
1313
+ if echo "$MRG" | grep -q 'exit=0' \
1314
+ && [ "$CONFLICTS" = "0" ] \
1315
+ && grep -q '^status: shipped$' "$ORIGIN/.planning/increments/inc-0001-alpha.md" \
1316
+ && grep -q '^status: shipped$' "$ORIGIN/.planning/increments/inc-0002-beta.md" \
1317
+ && [ "$STATE_TRACKED" = "0" ]; then
1318
+ pass "two-actor: ship A on feat-a + B on feat-b → merge to main with ZERO conflict, STATE.md never committed"
1319
+ else
1320
+ fail_case "two-actor zero-conflict" "merge=$MRG conflicts=$CONFLICTS state_tracked=$STATE_TRACKED"
1321
+ fi
1322
+ rm -rf "$ORIGIN" "$CA" "$CB"
1323
+ else
1324
+ pass "two-actor keystone skipped (git unavailable)"
1325
+ fi
1326
+
1327
+ # 71. id guard: a path-traversal --id is rejected before reaching the filesystem
1328
+ TMP=$(make_project)
1329
+ (cd "$TMP" && $NODE "$STATE_JS" migrate >/dev/null 2>&1)
1330
+ TRAV=$(cd "$TMP" && $NODE "$STATE_JS" claim --id "../../etc/passwd" --branch x 2>&1)
1331
+ if echo "$TRAV" | grep -q '"error": "INVALID_ID"'; then
1332
+ pass "claim rejects a path-traversal --id (INVALID_ID)"
1333
+ else
1334
+ fail_case "id traversal guard" "out=$TRAV"
1335
+ fi
1336
+
1161
1337
  # ─── Summary ─────────────────────────────────────────────
1162
1338
  echo ""
1163
1339
  echo "=== Results: $PASS passed, $FAIL failed ==="