qatools-cli 1.1.2 → 1.1.4
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.
- package/package.json +1 -1
- package/qatools.js +136 -0
package/package.json
CHANGED
package/qatools.js
CHANGED
|
@@ -903,6 +903,14 @@ program
|
|
|
903
903
|
next: `qatools requirement-update -i <id> --status approved (or refine AC first)`,
|
|
904
904
|
});
|
|
905
905
|
}
|
|
906
|
+
if (data.agenda.accessModelMissing) {
|
|
907
|
+
sections.push({
|
|
908
|
+
icon: "⚪",
|
|
909
|
+
title: "No access model defined for this project",
|
|
910
|
+
items: [" • Who can sign in? Which roles exist? What's protected? How is data isolated?"],
|
|
911
|
+
next: `qatools access show (shows a minimal example) → qatools access set -f model.json`,
|
|
912
|
+
});
|
|
913
|
+
}
|
|
906
914
|
|
|
907
915
|
if (sections.length === 0) {
|
|
908
916
|
console.log(" ✓ Nothing on the agenda. The project is in a clean state.\n");
|
|
@@ -1370,6 +1378,116 @@ program
|
|
|
1370
1378
|
process.exit(0);
|
|
1371
1379
|
});
|
|
1372
1380
|
|
|
1381
|
+
// access subcommands grouped under 'access'
|
|
1382
|
+
const accessCmd = program
|
|
1383
|
+
.command("access")
|
|
1384
|
+
.description("Manage the project's Access Model (who-can-do-what, structured)");
|
|
1385
|
+
|
|
1386
|
+
accessCmd
|
|
1387
|
+
.command("show")
|
|
1388
|
+
.description("Show the access model for the project")
|
|
1389
|
+
.option("--project <id>", "Target project ID (overrides default)")
|
|
1390
|
+
.option("-o, --output <path>", "Save to JSON file")
|
|
1391
|
+
.option("--json", "Output JSON to stdout")
|
|
1392
|
+
.option("-e, --email <email>", "Login email")
|
|
1393
|
+
.option("-p, --password <password>", "Login password")
|
|
1394
|
+
.action(async (options) => {
|
|
1395
|
+
await authenticate(options);
|
|
1396
|
+
const creds = getCredentials();
|
|
1397
|
+
const projectId = options.project || creds?.projectId;
|
|
1398
|
+
if (!projectId) { console.error("No project configured. Run: qatools setup, or pass --project <id>"); process.exit(1); }
|
|
1399
|
+
const getModel = httpsCallable(functions, "getAccessModel");
|
|
1400
|
+
let result;
|
|
1401
|
+
try { result = await getModel({ projectId }); }
|
|
1402
|
+
catch (e) { console.error("Failed:", e.message); process.exit(1); }
|
|
1403
|
+
const model = result.data.accessModel;
|
|
1404
|
+
if (!model) {
|
|
1405
|
+
console.log(`No access model defined for this project.`);
|
|
1406
|
+
console.log(`Define one by writing a JSON file and running: qatools access set -f model.json`);
|
|
1407
|
+
console.log(``);
|
|
1408
|
+
console.log(`Minimal shape:`);
|
|
1409
|
+
console.log(JSON.stringify({
|
|
1410
|
+
authMethods: [{ key: "google", description: "Google OAuth", primary: true }],
|
|
1411
|
+
roles: [{ key: "owner", description: "Project owner with full control" }],
|
|
1412
|
+
resources: [{ key: "project", description: "Project metadata", ownedBy: "project", permissions: [{ role: "owner", actions: ["read","create","update","delete"] }] }],
|
|
1413
|
+
isolationRules: [{ scope: "project", description: "Data scoped by projectId field", enforcedBy: ["firestore.rules", "checkProjectAccess()"] }],
|
|
1414
|
+
}, null, 2));
|
|
1415
|
+
process.exit(0);
|
|
1416
|
+
}
|
|
1417
|
+
if (options.output) {
|
|
1418
|
+
fs.writeFileSync(path.resolve(options.output), JSON.stringify(model, null, 2));
|
|
1419
|
+
console.log(`Saved to ${options.output}`);
|
|
1420
|
+
process.exit(0);
|
|
1421
|
+
}
|
|
1422
|
+
if (options.json) {
|
|
1423
|
+
console.log(JSON.stringify(model, null, 2));
|
|
1424
|
+
process.exit(0);
|
|
1425
|
+
}
|
|
1426
|
+
// Pretty text output
|
|
1427
|
+
console.log(``);
|
|
1428
|
+
console.log(`Access Model — project ${projectId}`);
|
|
1429
|
+
console.log(``);
|
|
1430
|
+
if (model.authMethods?.length) {
|
|
1431
|
+
console.log(`AUTH METHODS`);
|
|
1432
|
+
for (const a of model.authMethods) console.log(` ${a.primary ? "*" : " "} ${a.key.padEnd(15)} ${a.description}`);
|
|
1433
|
+
console.log(``);
|
|
1434
|
+
}
|
|
1435
|
+
if (model.roles?.length) {
|
|
1436
|
+
console.log(`ROLES`);
|
|
1437
|
+
for (const r of model.roles) {
|
|
1438
|
+
const inh = r.inherits?.length ? ` (inherits: ${r.inherits.join(", ")})` : "";
|
|
1439
|
+
console.log(` ${r.key.padEnd(15)} ${r.description}${inh}`);
|
|
1440
|
+
}
|
|
1441
|
+
console.log(``);
|
|
1442
|
+
}
|
|
1443
|
+
if (model.resources?.length) {
|
|
1444
|
+
console.log(`RESOURCES`);
|
|
1445
|
+
for (const r of model.resources) {
|
|
1446
|
+
const coll = r.collection ? ` [${r.collection}]` : "";
|
|
1447
|
+
console.log(` ${r.key}${coll} — owned by ${r.ownedBy}`);
|
|
1448
|
+
console.log(` ${r.description}`);
|
|
1449
|
+
for (const p of (r.permissions || [])) {
|
|
1450
|
+
const cond = p.condition ? ` (${p.condition})` : "";
|
|
1451
|
+
console.log(` ${p.role.padEnd(15)} ${p.actions.join(",")}${cond}`);
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
console.log(``);
|
|
1455
|
+
}
|
|
1456
|
+
if (model.isolationRules?.length) {
|
|
1457
|
+
console.log(`ISOLATION RULES`);
|
|
1458
|
+
for (const i of model.isolationRules) {
|
|
1459
|
+
console.log(` [${i.scope}] ${i.description}`);
|
|
1460
|
+
if (i.enforcedBy?.length) console.log(` enforced by: ${i.enforcedBy.join(", ")}`);
|
|
1461
|
+
}
|
|
1462
|
+
console.log(``);
|
|
1463
|
+
}
|
|
1464
|
+
process.exit(0);
|
|
1465
|
+
});
|
|
1466
|
+
|
|
1467
|
+
accessCmd
|
|
1468
|
+
.command("set")
|
|
1469
|
+
.description("Upload (replace) the entire access model from a JSON file")
|
|
1470
|
+
.requiredOption("-f, --file <path>", "Path to access model JSON")
|
|
1471
|
+
.option("--project <id>", "Target project ID (overrides default)")
|
|
1472
|
+
.option("-e, --email <email>", "Login email")
|
|
1473
|
+
.option("-p, --password <password>", "Login password")
|
|
1474
|
+
.action(async (options) => {
|
|
1475
|
+
await authenticate(options);
|
|
1476
|
+
const creds = getCredentials();
|
|
1477
|
+
const projectId = options.project || creds?.projectId;
|
|
1478
|
+
if (!projectId) { console.error("No project configured. Run: qatools setup, or pass --project <id>"); process.exit(1); }
|
|
1479
|
+
const model = JSON.parse(fs.readFileSync(path.resolve(options.file), "utf8"));
|
|
1480
|
+
const saveModel = httpsCallable(functions, "saveAccessModel");
|
|
1481
|
+
try {
|
|
1482
|
+
const result = await saveModel({ projectId, model });
|
|
1483
|
+
console.log(`Access model saved (${result.data.id}) — project ${projectId}`);
|
|
1484
|
+
} catch (e) {
|
|
1485
|
+
console.error("Failed:", e.message);
|
|
1486
|
+
process.exit(1);
|
|
1487
|
+
}
|
|
1488
|
+
process.exit(0);
|
|
1489
|
+
});
|
|
1490
|
+
|
|
1373
1491
|
program
|
|
1374
1492
|
.command("epics")
|
|
1375
1493
|
.description("List epics for the project")
|
|
@@ -1863,6 +1981,12 @@ Every claim, verdict, analysis, and recommendation MUST cite specific evidence:
|
|
|
1863
1981
|
|
|
1864
1982
|
## If you're unsure
|
|
1865
1983
|
Say "I'm unsure" — that's better than guessing. Suggest what evidence would resolve the uncertainty.
|
|
1984
|
+
|
|
1985
|
+
## Specialized skills
|
|
1986
|
+
|
|
1987
|
+
There is a specific skill for **algorithmic and numerical** bugs: \`qa-numerical-verification.md\`. Read it when you're fixing a formula or any code where the bug is "wrong number" rather than "wrong structure".
|
|
1988
|
+
|
|
1989
|
+
If you encounter a different recurring bug class that needs its own verification pattern (UI regressions, race conditions, data integrity, etc.), create a NEW specific skill (\`qa-<class>-verification.md\`) rather than expanding this file or building a monolithic "fix-checklist". The pattern is: principle here, specific category in its own skill, CLI enforces evidence regardless of category. Don't build a generic checklist for hypothetical categories — let real biting cases drive the need.
|
|
1866
1990
|
`,
|
|
1867
1991
|
"qa-numerical-verification.md": `---
|
|
1868
1992
|
description: How to verify algorithmic and numerical bug fixes. Read this when fixing a formula, calculation, or any code where the bug is "wrong number" rather than "wrong structure".
|
|
@@ -1874,6 +1998,18 @@ Most evidence-first examples deal with structural bugs (NullReferenceException,
|
|
|
1874
1998
|
|
|
1875
1999
|
This skill is for those bugs. The verification pattern is non-negotiable.
|
|
1876
2000
|
|
|
2001
|
+
## Before you start — entry-point checklist
|
|
2002
|
+
|
|
2003
|
+
Stop here and answer these BEFORE writing any verification code:
|
|
2004
|
+
|
|
2005
|
+
- [ ] Which input or test case actually triggers the bug? (Not "the system" — name a specific input.)
|
|
2006
|
+
- [ ] Does that input have non-zero, non-default values for the field the bug touches? (If everything is 0 or NULL, multiplying by anything gives the same result — the input cannot discriminate buggy from fixed code.)
|
|
2007
|
+
- [ ] If your input is non-discriminating: change it, or pick a different input. Never proceed with "the data doesn't show it".
|
|
2008
|
+
- [ ] Where will you write the expected and actual values? Answer: in the --evidence flag when you set status. If the values are large, also save them to a file you reference in evidence ("see /tmp/verify-2026-04-08.txt").
|
|
2009
|
+
- [ ] Can you compute the expected value by hand from the formula in under 60 seconds? If not, the formula is too complex for this verification — break it into pieces and verify each.
|
|
2010
|
+
|
|
2011
|
+
If any answer is "I don't know", stop and ask. Don't guess.
|
|
2012
|
+
|
|
1877
2013
|
## The pattern
|
|
1878
2014
|
|
|
1879
2015
|
1. **Trivialize the input.** Pick values that make the formula readable. Set multipliers to 1, set inputs to round numbers (10, 100), set toggles to known states.
|