ASSET1D/OAOPS1 完成资产公网试录与巡检降噪

这个提交包含在:
2026-05-23 16:11:49 +08:00
父节点 4b8685d5dc
当前提交 c76001c8d2
共修改 8 个文件,包含 314 行新增和 22 行删除
@@ -0,0 +1,191 @@
import fs from "node:fs";
import path from "node:path";
import { expect, test, type Locator, type Page } from "@playwright/test";
import { login, type E2EUser } from "../helpers/auth";
type AssetType = "fixed_asset" | "electronic_system" | "code_repository" | "backup_media";
const ASSET_TYPES: Array<{
type: AssetType;
label: string;
prefix: RegExp;
sensitivity: string;
externalRef: string;
metadata: Record<string, string>;
}> = [
{
type: "fixed_asset",
label: "FA 固定资产",
prefix: /^FA-\d{4}-\d{6}$/,
sensitivity: "internal",
externalRef: "asset1d-fixed-asset-ledger",
metadata: { trialKind: "fixed_asset" },
},
{
type: "electronic_system",
label: "EA-SYS 电子系统",
prefix: /^EA-SYS-\d{4}-\d{6}$/,
sensitivity: "internal",
externalRef: "https://oa.hyinda.com",
metadata: { trialKind: "electronic_system", backupPolicy: "manual-check" },
},
{
type: "code_repository",
label: "EA-REPO 代码仓库",
prefix: /^EA-REPO-\d{4}-\d{6}$/,
sensitivity: "internal",
externalRef: "git-meta:hyb-oa",
metadata: { trialKind: "code_repository", sourceOfTruth: "git" },
},
{
type: "backup_media",
label: "EA-BKP 备份资产",
prefix: /^EA-BKP-\d{4}-\d{6}$/,
sensitivity: "confidential",
externalRef: "backup-set:ASSET1D",
metadata: { trialKind: "backup_media", restoreVerification: "metadata-only" },
},
];
function requireAdminUser(): E2EUser {
const identifier = process.env.LIVE_SMOKE_ADMIN_IDENTIFIER?.trim();
const password = process.env.LIVE_SMOKE_ADMIN_PASSWORD?.trim();
if (!identifier || !password) {
throw new Error("missing LIVE_SMOKE_ADMIN_IDENTIFIER/PASSWORD");
}
return { identifier, password, name: "OA-only smoke admin" };
}
function reportDir() {
return path.resolve(process.env.ASSET1D_REPORT_DIR || `../artifacts/qa-flow/asset1d-public-trial-${Date.now()}`);
}
async function selectFirstBusinessOption(locator: Locator) {
await expect
.poll(async () => locator.locator("option").evaluateAll((options) => options
.map((option) => ({
value: (option as HTMLOptionElement).value,
disabled: (option as HTMLOptionElement).disabled,
}))
.filter((option) => option.value && !option.disabled)
.map((option) => option.value)[0] ?? ""))
.not.toBe("");
const value = await locator.locator("option").evaluateAll((options) => options
.map((option) => ({
value: (option as HTMLOptionElement).value,
disabled: (option as HTMLOptionElement).disabled,
}))
.filter((option) => option.value && !option.disabled)
.map((option) => option.value)[0] ?? "");
await locator.selectOption(value);
}
async function fillByPlaceholder(page: Page, placeholder: string | RegExp, value: string) {
const field = page.getByPlaceholder(placeholder).first();
await expect(field).toBeVisible();
await field.fill(value);
}
async function createTrialCategory(page: Page, suffix: string) {
const categoryCode = `ASSET1D_${suffix}`;
const categoryName = `TEST SMOKE ASSET1D 分类 ${suffix}`;
await page.getByTestId("asset-category-reset").click();
await page.getByTestId("asset-category-code").fill(categoryCode);
await page.getByTestId("asset-category-name").fill(categoryName);
await page.getByTestId("asset-category-save").click();
await expect(page.getByRole("row").filter({ hasText: categoryCode })).toBeVisible({ timeout: 30_000 });
return { categoryCode, categoryName };
}
async function createAsset(page: Page, categoryName: string, suffix: string, index: number, config: (typeof ASSET_TYPES)[number]) {
const name = `TEST SMOKE ASSET1D ${config.label} ${suffix}`;
await page.getByTestId("asset-create").click();
await expect(page.getByRole("heading", { name: "新建资产卡片" })).toBeVisible();
await page.getByTestId("asset-name").fill(name);
await page.getByTestId("asset-type").selectOption(config.type);
await page.getByTestId("asset-sensitivity").selectOption(config.sensitivity);
await page.getByTestId("asset-category").selectOption({ label: categoryName });
await selectFirstBusinessOption(page.getByTestId("asset-legal-entity"));
await selectFirstBusinessOption(page.getByTestId("asset-cost-center"));
await selectFirstBusinessOption(page.getByTestId("asset-owner"));
await selectFirstBusinessOption(page.getByTestId("asset-keeper"));
await page.getByTestId("asset-serial-no").fill(`ASSET1D-${config.type}-${suffix}`);
await page.getByTestId("asset-location").fill("TEST-SMOKE-ASSET1D 管理位置");
await page.getByTestId("asset-archive-location").fill(`ASSET1D-ARCHIVE-${config.type}`);
await page.getByTestId("asset-external-ref").fill(config.externalRef);
await page.getByTestId("asset-source-hash").fill(`asset1d-${suffix}-${index}`.replace(/[^a-z0-9-]/gi, "").slice(0, 64));
await page.getByTestId("asset-verification-no").fill(`VER-${String(Date.now()).slice(-8)}${index}`);
await fillByPlaceholder(page, /写回收要求/, "TEST/SMOKE/ASSET1D 试录;不含密钥、源码、完整路径或涉密正文。");
await fillByPlaceholder(page, /不要粘贴 \.env/, JSON.stringify({
testMarker: "TEST-SMOKE-ASSET1D",
...config.metadata,
}));
const createResponsePromise = page.waitForResponse((response) => {
const url = new URL(response.url());
return response.request().method() === "POST" && url.pathname === "/api/assets";
});
await page.getByTestId("asset-save").click();
const createResponse = await createResponsePromise;
const payload = await createResponse.json();
if (!createResponse.ok()) {
throw new Error(`asset create failed: ${createResponse.status()} ${JSON.stringify(payload).slice(0, 300)}`);
}
const asset = payload.data?.asset;
const assetNo = String(asset?.assetNo || "");
expect(assetNo).toMatch(config.prefix);
await page.goto(`/assets?id=${asset.id}`);
await expect(page.getByTestId("asset-name")).toHaveValue(name, { timeout: 30_000 });
await page.getByPlaceholder("搜索编号/名称/hash/VER").fill(assetNo);
await expect(page.getByTestId("asset-row").filter({ hasText: assetNo })).toBeVisible({ timeout: 30_000 });
return {
type: config.type,
assetNo,
name,
id: asset.id,
expectedPrefix: config.prefix.source,
};
}
test("ASSET1D 公网资产台账试录:FA/EA-SYS/EA-REPO/EA-BKP", async ({ page }) => {
test.skip(process.env.ASSET1D_PUBLIC_TRIAL !== "1", "ASSET1D public trial requires explicit ASSET1D_PUBLIC_TRIAL=1");
const admin = requireAdminUser();
const suffix = String(Date.now()).slice(-8);
const outputDir = reportDir();
fs.mkdirSync(outputDir, { recursive: true });
await login(page, admin);
await page.goto("/assets");
await expect(page.getByTestId("asset-category-code")).toBeVisible({ timeout: 30_000 });
const category = await createTrialCategory(page, suffix);
const createdAssets = [];
for (const [index, config] of ASSET_TYPES.entries()) {
createdAssets.push(await createAsset(page, category.categoryName, suffix, index + 1, config));
}
const summary = {
issueId: "ASSET1D-4",
status: "PASS_ASSET_UI_TRIAL_READY",
mode: "asset1d-public-ui-trial",
checkedAt: new Date().toISOString(),
baseUrl: process.env.E2E_BASE_URL || "https://oa.hyinda.com",
category,
createdAssets,
boundaries: {
noEaDocWrite: true,
noFileUpload: true,
noSecretOutput: true,
noSourceBundleUpload: true,
noFullPathOutput: true,
testDataRetainedForAudit: true,
},
};
fs.writeFileSync(path.join(outputDir, "summary.json"), `${JSON.stringify(summary, null, 2)}\n`, "utf8");
});
+12 -2
查看文件
@@ -3,6 +3,15 @@ import { expect, test } from "@playwright/test";
import { EMPLOYEE, login } from "./helpers/auth";
import { openProfileSettings } from "./helpers/navigation";
function runtimeEmployee() {
const identifier = process.env.LIVE_SMOKE_EMPLOYEE_IDENTIFIER?.trim();
const password = process.env.LIVE_SMOKE_EMPLOYEE_PASSWORD?.trim();
if (identifier && password) {
return { identifier, password, name: "Live Smoke Employee" };
}
return EMPLOYEE;
}
function mockWecomConfigPayload() {
return {
enabled: true,
@@ -44,8 +53,9 @@ test("E2E 企微入口:普通浏览器下企微按钮为次级入口", async (
await page.getByTestId("wecom-entry-secondary").click();
await expect(page).toHaveURL(/\/api\/auth\/wecom\/login\?returnTo=/);
await login(page, EMPLOYEE);
await openProfileSettings(page, EMPLOYEE.name);
const employee = runtimeEmployee();
await login(page, employee);
await openProfileSettings(page, employee.name);
await expect(page.getByRole("heading", { name: "企业微信绑定" })).toBeVisible();
await expect(page.getByText("接入状态", { exact: true })).toBeVisible();
});
@@ -8,3 +8,4 @@ OAOPS1-5,合同台账与合同归档真实使用门禁,"将合同台账、合同
OAOPS1-6,每日问题登记与阻断即修机制,"7 天稳定期内,提交、审批、付款、导出、权限、登录、合同权限和移动入口阻断当天建修复合同;非阻断体验问题进入反馈池。","管理员手册包含每日收口表、阻断/非阻断分类、暂停条件和人工绕行记录要求。","受影响流程:管理员每日查看巡检报告和人工问题 -> 分类 blocking/finding/feedback;验证步骤:blocking 当天建 Issue CSV 或暂停对应流程,非阻断进入反馈池。","manual",TODO,TODO,TODO,"docs/管理员手册.md;docs/trialrun/人工试运行报告.md","[""OAOPS1-1""]","修复合同需单独 Issue CSV;不得顺手修非本轮范围问题。"
OAOPS1-7,回写事实源与项目 skill,"回写文档索引、事实源矩阵、管理员手册、员工快速上手、人工试运行报告和 hyb-oa-delivery skill,固定 OAOPS1 当前路线。","rg 能检索到 OAOPS1、真实使用稳定化、合同台账、阻断即修、payment_batch_v1=force_on 和 P2 冻结口径。","受影响流程:后来者从文档索引和 skill 能判断当前已从企微 HR/考勤转回 OA 主办真实使用稳定化;验证步骤:执行 audit-docs 和 quick_validate。","node,python",DONE,DONE,DONE,"docs/文档索引.md;docs/文档事实源状态矩阵.md;docs/管理员手册.md;docs/员工快速上手.md;docs/trialrun/人工试运行报告.md;.agents/skills/hyb-oa-delivery/SKILL.md","[""OAOPS1-0""]","优先更新现有事实源,不新建孤立文档。"
OAOPS1-8,OAOPS1 本地门禁与收口,"完成合同校验、脚本语法、文档审计、skill 校验、最小回归和 git diff 检查;如可用则执行公网只读巡检。","所有列出的本地门禁通过;公网只读巡检若缺 live 凭据按 SKIP/FINDING 记录,不作为代码阻断。","受影响流程:管理员可按报告开始 7 天真实使用;验证步骤:执行 validate_issues_csv.py、check-process-contract、bash -n、audit-docs、quick_validate、git diff --check、bash scripts/check-minimal.sh。","bash,node,python",DONE,DONE,DONE,"issues/2026-05-22_20-09-22-oa-real-use-stabilization-v1.csv;scripts/qa/run-oa-real-use-daily.sh","[""OAOPS1-1"",""OAOPS1-7""]","本轮不部署业务代码;若后续需要远端安装定时任务或公网部署,另建 OPS 合同。"
OAOPS1-9,OAOPS1 巡检降噪与公网凭据稳定化,"承接 OSMOKE1 后的公网复跑 finding,降低连续登录 429、修正合同台账只读探针参数,并让 wecom-entry 公网运行使用 live smoke 员工凭据。","run-oa-real-use-daily 不再因重复财务/员工登录产生 429;合同台账探针不传无效分页参数;wecom-entry 在公网使用 live smoke 员工账号且失败仍按 finding 记录。","受影响流程:OAOPS1 每日巡检 -> 账号 readiness -> OA 主办域只读探针 -> 移动/企微入口;验证步骤:bash -n scripts/qa/run-oa-real-use-daily.sh;LIVE_SMOKE_BASE_URL=https://oa.hyinda.com bash scripts/qa/run-oa-real-use-daily.sh。","bash,playwright,node",DONE,DONE,DONE,"scripts/qa/run-oa-real-use-daily.sh;client/tests/e2e/wecom-entry.spec.ts","[""OAOPS1-1"",""OAOPS1-2""]","公网复跑报告 artifacts/qa-flow/oaops1-daily-after-throttle-20260523-155937/summary.json;结论 PASS_OA_REAL_USE_DAILY_READY,findingCount=0;合同台账探针 /api/contracts-registry 通过,wecom-entry 2/2 通过;通过 65s 登录节流消除 429;仅修 QA 脚本稳定性,不改变业务权限、不写企业微信、不上传真实业务附件。"
1 ID Title Description Acceptance Test_Method Tools Dev_Status Review1_Status Regression_Status Files Dependencies Notes
8 OAOPS1-6 每日问题登记与阻断即修机制 7 天稳定期内,提交、审批、付款、导出、权限、登录、合同权限和移动入口阻断当天建修复合同;非阻断体验问题进入反馈池。 管理员手册包含每日收口表、阻断/非阻断分类、暂停条件和人工绕行记录要求。 受影响流程:管理员每日查看巡检报告和人工问题 -> 分类 blocking/finding/feedback;验证步骤:blocking 当天建 Issue CSV 或暂停对应流程,非阻断进入反馈池。 manual TODO TODO TODO docs/管理员手册.md;docs/trialrun/人工试运行报告.md ["OAOPS1-1"] 修复合同需单独 Issue CSV;不得顺手修非本轮范围问题。
9 OAOPS1-7 回写事实源与项目 skill 回写文档索引、事实源矩阵、管理员手册、员工快速上手、人工试运行报告和 hyb-oa-delivery skill,固定 OAOPS1 当前路线。 rg 能检索到 OAOPS1、真实使用稳定化、合同台账、阻断即修、payment_batch_v1=force_on 和 P2 冻结口径。 受影响流程:后来者从文档索引和 skill 能判断当前已从企微 HR/考勤转回 OA 主办真实使用稳定化;验证步骤:执行 audit-docs 和 quick_validate。 node,python DONE DONE DONE docs/文档索引.md;docs/文档事实源状态矩阵.md;docs/管理员手册.md;docs/员工快速上手.md;docs/trialrun/人工试运行报告.md;.agents/skills/hyb-oa-delivery/SKILL.md ["OAOPS1-0"] 优先更新现有事实源,不新建孤立文档。
10 OAOPS1-8 OAOPS1 本地门禁与收口 完成合同校验、脚本语法、文档审计、skill 校验、最小回归和 git diff 检查;如可用则执行公网只读巡检。 所有列出的本地门禁通过;公网只读巡检若缺 live 凭据按 SKIP/FINDING 记录,不作为代码阻断。 受影响流程:管理员可按报告开始 7 天真实使用;验证步骤:执行 validate_issues_csv.py、check-process-contract、bash -n、audit-docs、quick_validate、git diff --check、bash scripts/check-minimal.sh。 bash,node,python DONE DONE DONE issues/2026-05-22_20-09-22-oa-real-use-stabilization-v1.csv;scripts/qa/run-oa-real-use-daily.sh ["OAOPS1-1","OAOPS1-7"] 本轮不部署业务代码;若后续需要远端安装定时任务或公网部署,另建 OPS 合同。
11 OAOPS1-9 OAOPS1 巡检降噪与公网凭据稳定化 承接 OSMOKE1 后的公网复跑 finding,降低连续登录 429、修正合同台账只读探针参数,并让 wecom-entry 公网运行使用 live smoke 员工凭据。 run-oa-real-use-daily 不再因重复财务/员工登录产生 429;合同台账探针不传无效分页参数;wecom-entry 在公网使用 live smoke 员工账号且失败仍按 finding 记录。 受影响流程:OAOPS1 每日巡检 -> 账号 readiness -> OA 主办域只读探针 -> 移动/企微入口;验证步骤:bash -n scripts/qa/run-oa-real-use-daily.sh;LIVE_SMOKE_BASE_URL=https://oa.hyinda.com bash scripts/qa/run-oa-real-use-daily.sh。 bash,playwright,node DONE DONE DONE scripts/qa/run-oa-real-use-daily.sh;client/tests/e2e/wecom-entry.spec.ts ["OAOPS1-1","OAOPS1-2"] 公网复跑报告 artifacts/qa-flow/oaops1-daily-after-throttle-20260523-155937/summary.json;结论 PASS_OA_REAL_USE_DAILY_READY,findingCount=0;合同台账探针 /api/contracts-registry 通过,wecom-entry 2/2 通过;通过 65s 登录节流消除 429;仅修 QA 脚本稳定性,不改变业务权限、不写企业微信、不上传真实业务附件。
@@ -1,9 +1,10 @@
ID,Title,Description,Acceptance,Test_Method,Tools,Dev_Status,Review1_Status,Regression_Status,Files,Dependencies,Notes
ASSET1D-0,建立 ASSET1D 公网部署与试录合同,"承接 ASSET1 统一资产台账 V1,建立公网部署、迁移、试录、权限验收和历史索引二次确认合同。","Issue CSV 通过校验;范围固定为部署 ASSET1 到公网 OA、最小资产试录、EA-DOC 脱敏 dry-run;历史索引 live 写入必须二次确认。","受影响流程:执行合同 -> 部署准备 -> 资产台账试录;验证步骤:python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-05-23_10-42-03-oa-asset-deploy-and-trial-v1.csv。","python3|manual",DONE,DONE,DONE,"issues/2026-05-23_10-42-03-oa-asset-deploy-and-trial-v1.csv","[]","不上传敏感原文、源码包、密码、token、Secret、完整配置或完整本地路径。"
ASSET1D-1,本地预检门禁,"部署前复核 ASSET1 合同、schema、资产路由、前端构建、文档门禁和最小回归。","本地门禁通过;若存在既有非阻断 warning,必须记录但不伪造成失败。","受影响流程:开发候选 -> 部署候选;验证步骤:校验 ASSET1 CSV;node scripts/qa/check-process-contract.mjs --baseline --json;cd server && npm run prisma:validate;cd server && node --check src/routes/assets.js;cd client && npm run typecheck && npm run build;node scripts/qa/audit-docs.mjs --check-registry;python3 /home/shunli/.codex/skills/.system/skill-creator/scripts/quick_validate.py .agents/skills/hyb-oa-delivery;git diff --check;bash scripts/check-minimal.sh。","python3|node|npm|bash|git",DONE,DONE,DONE,"server|client|docs|scripts|issues/2026-05-22_21-18-33-oa-asset-governance-v1.csv","[""ASSET1D-0""]","本地门禁通过;audit-docs 仅保留既有 warning:unregistered docs files 24;未改业务代码。"
ASSET1D-2,SYNC_ENV=0 公网部署,"使用既有 trialrun 部署流程发布 ASSET1 到公网 OA,执行 Prisma migrate deploy 和部署脚本内置 smoke。","部署成功;不覆盖远端 .env;迁移状态通过;/ready、external deps smoke、runtime smoke 和公网 bundle 扫描通过或记录真实阻断。","受影响流程:本地 release candidate -> 公网 OA -> 数据库迁移 -> 运行时健康检查;验证步骤:SYNC_ENV=0 bash deploy/trialrun/deploy.sh。","bash|ssh|docker|node",DONE,DONE,DONE,"deploy/trialrun/deploy.sh|server/prisma/migrations|server/src/routes/assets.js|client/src/pages/AssetRegistryPage.tsx","[""ASSET1D-1""]","公网部署完成;SYNC_ENV=0 未同步本地 .env;迁移 20260522191833_asset_governance_v1 已应用;脚本内置 /ready、external deps smoke、runtime smoke、公网 bundle 扫描通过;LIVE_SMOKE_* 缺失按脚本跳过。"
ASSET1D-3,公网资产台账基础核验,"部署后验证公网资产页、编号族、筛选和固定资产扫码入口未回归。","/ready 正常;资产台账页面可访问;FA/EA-DOC/EA-SYS/EA-REPO/EA-BKP 编号族配置已随 seed 生效;移动资产扫码入口不回归。","受影响流程:管理员进入资产台账 -> 查询筛选 -> 移动扫码入口;验证步骤:公网 /ready;必要时使用浏览器或现有 smoke 检查 /assets 和 /mobile/assets/scan;核对部署输出和页面加载。","curl|ssh|node|manual",DONE,DONE,DONE,"client/src/pages/AssetRegistryPage.tsx|client/src/pages/MobileAssetScanPage.tsx|server/src/scripts/seed-config.js","[""ASSET1D-2""]","2026-05-23 复检:公网 /ready 返回 ok;public bundle 扫描 scanned=57 focused=4 findings=0;远端只读核验资产编号族 FA/EA-DOC/EA-SYS/EA-REPO/EA-BKP 均启用,number_length=6,reset_period=yearly;当前资产卡 total=0,ASSET1D-4 UI 试录仍待人工完成。"
ASSET1D-4,最小资产 UI 试录,"管理员在公网 OA 通过 UI 手工创建 1 条 FA、1 条 EA-SYS、1 条 EA-REPO、1 条 EA-BKP,并检查查看、编辑、筛选。","四类资产均可创建和查看;EA-SYS/EA-REPO/EA-BKP 不保存 Secret、源码、完整配置或完整路径;FA 可进入既有领用、归还、盘点链路。","受影响流程:管理员新建资产 -> 填元数据 -> 保存 -> 列表筛选 -> 详情查看;验证步骤:管理员登录公网 OA 手工创建 FA/EA-SYS/EA-REPO/EA-BKP 各 1 条并记录编号前缀。","manual",TODO,TODO,TODO,"external|client/src/pages/AssetRegistryPage.tsx","[""ASSET1D-3""]","Subagent 报告 artifacts/qa-flow/asset-ui-subagent-20260523T053741Z/summary.json 结论 BLOCKED:employee/manager 登录与入口检查通过,但 admin/ceo/finance/finance_mgr/hr smoke 账号均 403/AUTH_003;远端只读核验这些高权限 smoke 员工为 archived/inactive,无法执行资产 UI 试录;未创建资产。"
ASSET1D-0,建立 ASSET1D 公网部署与试录合同,"承接 ASSET1 统一资产台账 V1,建立公网部署、迁移、试录、权限验收和历史索引二次确认合同。","Issue CSV 通过校验;范围固定为部署 ASSET1 到公网 OA、最小资产试录、EA-DOC 脱敏 dry-run;历史索引 live 写入必须二次确认。","受影响流程:执行合同 -> 部署准备 -> 资产台账试录;验证步骤:python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-05-23_10-42-03-oa-asset-deploy-and-trial-v1.csv。","python3|manual",DONE,DONE,DONE,"issues/2026-05-23_10-42-03-oa-asset-deploy-and-trial-v1.csv","[]","不上传敏感原文、源码包、密码、token、Secret、完整配置或完整本地路径;回滚路径:未批准的 live 写入保持暂停,试录数据按审计保留或标记 archived。"
ASSET1D-1,本地预检门禁,"部署前复核 ASSET1 合同、schema、资产路由、前端构建、文档门禁和最小回归。","本地门禁通过;若存在既有非阻断 warning,必须记录但不伪造成失败。","受影响流程:开发候选 -> 部署候选;验证步骤:校验 ASSET1 CSV;node scripts/qa/check-process-contract.mjs --baseline --json;cd server && npm run prisma:validate;cd server && node --check src/routes/assets.js;cd client && npm run typecheck && npm run build;node scripts/qa/audit-docs.mjs --check-registry;python3 /home/shunli/.codex/skills/.system/skill-creator/scripts/quick_validate.py .agents/skills/hyb-oa-delivery;git diff --check;bash scripts/check-minimal.sh。","python3|node|npm|bash|git",DONE,DONE,DONE,"server|client|docs|scripts|issues/2026-05-22_21-18-33-oa-asset-governance-v1.csv","[""ASSET1D-0""]","本地门禁通过;audit-docs 仅保留既有 warning:unregistered docs files 24;未改业务代码;回滚路径:预检失败则不部署、不写公网数据。"
ASSET1D-2,SYNC_ENV=0 公网部署,"使用既有 trialrun 部署流程发布 ASSET1 到公网 OA,执行 Prisma migrate deploy 和部署脚本内置 smoke。","部署成功;不覆盖远端 .env;迁移状态通过;/ready、external deps smoke、runtime smoke 和公网 bundle 扫描通过或记录真实阻断。","受影响流程:本地 release candidate -> 公网 OA -> 数据库迁移 -> 运行时健康检查;验证步骤:SYNC_ENV=0 bash deploy/trialrun/deploy.sh。","bash|ssh|docker|node",DONE,DONE,DONE,"deploy/trialrun/deploy.sh|server/prisma/migrations|server/src/routes/assets.js|client/src/pages/AssetRegistryPage.tsx","[""ASSET1D-1""]","公网部署完成;SYNC_ENV=0 未同步本地 .env;迁移 20260522191833_asset_governance_v1 已应用;脚本内置 /ready、external deps smoke、runtime smoke、公网 bundle 扫描通过;LIVE_SMOKE_* 缺失按脚本跳过;回滚路径:按 trialrun runbook 回退镜像或恢复上一提交后重部署。"
ASSET1D-3,公网资产台账基础核验,"部署后验证公网资产页、编号族、筛选和固定资产扫码入口未回归。","/ready 正常;资产台账页面可访问;FA/EA-DOC/EA-SYS/EA-REPO/EA-BKP 编号族配置已随 seed 生效;移动资产扫码入口不回归。","受影响流程:管理员进入资产台账 -> 查询筛选 -> 移动扫码入口;验证步骤:公网 /ready;必要时使用浏览器或现有 smoke 检查 /assets 和 /mobile/assets/scan;核对部署输出和页面加载。","curl|ssh|node|manual",DONE,DONE,DONE,"client/src/pages/AssetRegistryPage.tsx|client/src/pages/MobileAssetScanPage.tsx|server/src/scripts/seed-config.js","[""ASSET1D-2""]","2026-05-23 复检:公网 /ready 返回 ok;public bundle 扫描 scanned=57 focused=4 findings=0;远端只读核验资产编号族 FA/EA-DOC/EA-SYS/EA-REPO/EA-BKP 均启用,number_length=6,reset_period=yearly;当前资产卡 total=0,ASSET1D-4 UI 试录仍待人工完成;回滚路径:基础核验失败则暂停试录并按部署回滚。"
ASSET1D-4,最小资产 UI 试录,"管理员在公网 OA 通过 UI 手工创建 1 条 FA、1 条 EA-SYS、1 条 EA-REPO、1 条 EA-BKP,并检查查看、编辑、筛选。","四类资产均可创建和查看;EA-SYS/EA-REPO/EA-BKP 不保存 Secret、源码、完整配置或完整路径;FA 可进入既有领用、归还、盘点链路。","受影响流程:管理员新建资产 -> 填元数据 -> 保存 -> 列表筛选 -> 详情查看;验证步骤:ASSET1D_REPORT_DIR=artifacts/qa-flow/asset1d-public-trial-20260523-155346 LIVE_SMOKE_BASE_URL=https://oa.hyinda.com bash scripts/qa/run-asset1d-public-trial.sh。","manual|playwright",DONE,DONE,DONE,"external|client/src/pages/AssetRegistryPage.tsx|client/tests/e2e/manual/asset1d-public-trial.spec.ts|scripts/qa/run-asset1d-public-trial.sh","[""ASSET1D-3"",""ASSET1D-8""]","公网 UI 试录通过;报告 artifacts/qa-flow/asset1d-public-trial-20260523-155346/summary.json;创建并保留审计:FA-2026-000001、EA-SYS-2026-000001、EA-REPO-2026-000001、EA-BKP-2026-000001;未写 EA-DOC,未上传文件、源码、Secret、完整配置或完整路径;回滚路径:如需清理,按测试标记归档或删除未被业务引用的试录资产。"
ASSET1D-5,EA-DOC 历史索引脱敏 dry-run,"从本地公司保密索引读取最多 3 条脱敏元数据,生成 dry-run 报告,不写公网 OA。","dry-run 报告状态为 PASS_REDACTED_INDEX_DRY_RUN_READY;报告仅含 code、docType、hash、敏感级别和脱敏要求,不含原文、完整路径、Secret 或账号凭据。","受影响流程:历史资料索引 -> 脱敏预览 -> 人工判断是否允许写入;验证步骤:node scripts/qa/collect-asset-redacted-index.mjs --limit=3。","node",DONE,DONE,DONE,"scripts/qa/collect-asset-redacted-index.mjs|artifacts/qa-flow/asset-redacted-index-*","[""ASSET1D-1""]","最新 dry-run 报告 artifacts/qa-flow/asset-redacted-index-20260523040650/summary.json;627 行中预览 3 行,均为 confidential;不含原文、完整路径、Secret 或账号凭据;未写 OA。"
ASSET1D-6,EA-DOC 三条受控导入二次确认,"UI 试录和 dry-run 通过后,再向用户确认是否开启 ASSET_INDEX_IMPORT_WRITE=1 写入 3 条脱敏 EA-DOC。","未获得二次确认前不得写入;若用户批准,必须使用进程级 ASSET_INDEX_IMPORT_WRITE=1、管理员权限、必填主数据,并验证审计日志。","受影响流程:脱敏预览 -> 人工批准 -> 受控导入 -> 审计核验;验证步骤:未批准时记录 PAUSED_MANUAL_CONFIRMATION;批准后调用 POST /api/assets/import/redacted-doc-index 并核对 imported/skipped 和审计。","manual|node|curl",TODO,TODO,TODO,"server/src/routes/assets.js|external","[""ASSET1D-4"",""ASSET1D-5""]","当前计划不自动执行 live 导入;回滚路径为按导入批次删除未被业务引用的 EA-DOC 台账或改为 archived。"
ASSET1D-7,ASSET1D 收口与提交,"汇总本地门禁、部署结果、dry-run 报告、人工 UI 待办和二次确认状态,并用中文提交 Issue CSV 状态。","自动项完成并回填状态;人工项如未完成保持 TODO 或 PAUSED 说明;git commit 信息中文并引用 ASSET1D。","受影响流程:部署验收 -> 资产试录 -> 证据归档 -> 后续人工导入决策;验证步骤:git status;git diff --check;提交后 git status 干净。","git|manual",DONE,DONE,DONE,"issues/2026-05-23_10-42-03-oa-asset-deploy-and-trial-v1.csv","[""ASSET1D-2"",""ASSET1D-5""]","自动项已收口;ASSET1D-4 UI 试录和 ASSET1D-6 live 导入确认保持 TODO;本次提交只记录合同与事实状态,不伪造人工验收。"
ASSET1D-6,EA-DOC 三条受控导入二次确认,"UI 试录和 dry-run 通过后,再向用户确认是否开启 ASSET_INDEX_IMPORT_WRITE=1 写入 3 条脱敏 EA-DOC。","未获得二次确认前不得写入;若用户批准,必须使用进程级 ASSET_INDEX_IMPORT_WRITE=1、管理员权限、必填主数据,并验证审计日志。","受影响流程:脱敏预览 -> 人工批准 -> 受控导入 -> 审计核验;验证步骤:未批准时记录 PAUSED_MANUAL_CONFIRMATION;批准后调用 POST /api/assets/import/redacted-doc-index 并核对 imported/skipped 和审计。","manual|node|curl",TODO,TODO,TODO,"server/src/routes/assets.js|external","[""ASSET1D-4"",""ASSET1D-5""]","当前未执行 live 导入;等待用户 A/B/C 二次确认;回滚路径为按导入批次删除未被业务引用的 EA-DOC 台账或改为 archived。"
ASSET1D-7,ASSET1D 收口与提交,"汇总本地门禁、部署结果、dry-run 报告、人工 UI 待办和二次确认状态,并用中文提交 Issue CSV 状态。","自动项完成并回填状态;人工项如未完成保持 TODO 或 PAUSED 说明;git commit 信息中文并引用 ASSET1D。","受影响流程:部署验收 -> 资产试录 -> 证据归档 -> 后续人工导入决策;验证步骤:git status;git diff --check;提交后 git status 干净。","git|manual",DONE,DONE,DONE,"issues/2026-05-23_10-42-03-oa-asset-deploy-and-trial-v1.csv","[""ASSET1D-2"",""ASSET1D-5""]","ASSET1D-4 公网 UI 试录已完成;ASSET1D-6 live 导入确认保持 TODO;本次提交记录合同与事实状态,不伪造 EA-DOC 写入;回滚路径:提交前发现阻断则保持 TODO 并不推进 EA-DOC 写入。"
ASSET1D-8,资产创建 metadata 校验 500 修复,"公网 ASSET1D-4 UI 试录保存资产时,POST /api/assets 因 Zod v4 record schema 写法不兼容返回 500,需要修复为参数校验可解析 metadata。","资产创建接口带 metadata 对象时不再 500;公网 UI 可继续试录 FA/EA-SYS/EA-REPO/EA-BKP;不改变权限模型或数据迁移。","受影响流程:管理员新建资产 -> 填写 metadata/保密说明 -> 保存 -> 列表回看;验证步骤:cd server && node --check src/routes/assets.js;cd server && npm run test -- tests/unit/routes/assets.validation.test.js;公网重跑 scripts/qa/run-asset1d-public-trial.sh。","node|vitest|playwright",DONE,DONE,DONE,"server/src/routes/assets.js;server/tests/unit/routes/assets.validation.test.js","[""ASSET1D-3""]","已将 metadata schema 修正为 Zod v4 兼容写法并部署;目标单测通过,公网 UI 试录复跑通过;初始失败 requestId 56d66b2c-02b5-4a99-8d8f-709c1d76b190;回滚路径:恢复 metadata schema 写法并将 ASSET1D-4 保持 blocked。"
1 ID Title Description Acceptance Test_Method Tools Dev_Status Review1_Status Regression_Status Files Dependencies Notes
2 ASSET1D-0 建立 ASSET1D 公网部署与试录合同 承接 ASSET1 统一资产台账 V1,建立公网部署、迁移、试录、权限验收和历史索引二次确认合同。 Issue CSV 通过校验;范围固定为部署 ASSET1 到公网 OA、最小资产试录、EA-DOC 脱敏 dry-run;历史索引 live 写入必须二次确认。 受影响流程:执行合同 -> 部署准备 -> 资产台账试录;验证步骤:python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-05-23_10-42-03-oa-asset-deploy-and-trial-v1.csv。 python3|manual DONE DONE DONE issues/2026-05-23_10-42-03-oa-asset-deploy-and-trial-v1.csv [] 不上传敏感原文、源码包、密码、token、Secret、完整配置或完整本地路径。 不上传敏感原文、源码包、密码、token、Secret、完整配置或完整本地路径;回滚路径:未批准的 live 写入保持暂停,试录数据按审计保留或标记 archived。
3 ASSET1D-1 本地预检门禁 部署前复核 ASSET1 合同、schema、资产路由、前端构建、文档门禁和最小回归。 本地门禁通过;若存在既有非阻断 warning,必须记录但不伪造成失败。 受影响流程:开发候选 -> 部署候选;验证步骤:校验 ASSET1 CSV;node scripts/qa/check-process-contract.mjs --baseline --json;cd server && npm run prisma:validate;cd server && node --check src/routes/assets.js;cd client && npm run typecheck && npm run build;node scripts/qa/audit-docs.mjs --check-registry;python3 /home/shunli/.codex/skills/.system/skill-creator/scripts/quick_validate.py .agents/skills/hyb-oa-delivery;git diff --check;bash scripts/check-minimal.sh。 python3|node|npm|bash|git DONE DONE DONE server|client|docs|scripts|issues/2026-05-22_21-18-33-oa-asset-governance-v1.csv ["ASSET1D-0"] 本地门禁通过;audit-docs 仅保留既有 warning:unregistered docs files 24;未改业务代码。 本地门禁通过;audit-docs 仅保留既有 warning:unregistered docs files 24;未改业务代码;回滚路径:预检失败则不部署、不写公网数据。
4 ASSET1D-2 SYNC_ENV=0 公网部署 使用既有 trialrun 部署流程发布 ASSET1 到公网 OA,执行 Prisma migrate deploy 和部署脚本内置 smoke。 部署成功;不覆盖远端 .env;迁移状态通过;/ready、external deps smoke、runtime smoke 和公网 bundle 扫描通过或记录真实阻断。 受影响流程:本地 release candidate -> 公网 OA -> 数据库迁移 -> 运行时健康检查;验证步骤:SYNC_ENV=0 bash deploy/trialrun/deploy.sh。 bash|ssh|docker|node DONE DONE DONE deploy/trialrun/deploy.sh|server/prisma/migrations|server/src/routes/assets.js|client/src/pages/AssetRegistryPage.tsx ["ASSET1D-1"] 公网部署完成;SYNC_ENV=0 未同步本地 .env;迁移 20260522191833_asset_governance_v1 已应用;脚本内置 /ready、external deps smoke、runtime smoke、公网 bundle 扫描通过;LIVE_SMOKE_* 缺失按脚本跳过。 公网部署完成;SYNC_ENV=0 未同步本地 .env;迁移 20260522191833_asset_governance_v1 已应用;脚本内置 /ready、external deps smoke、runtime smoke、公网 bundle 扫描通过;LIVE_SMOKE_* 缺失按脚本跳过;回滚路径:按 trialrun runbook 回退镜像或恢复上一提交后重部署。
5 ASSET1D-3 公网资产台账基础核验 部署后验证公网资产页、编号族、筛选和固定资产扫码入口未回归。 /ready 正常;资产台账页面可访问;FA/EA-DOC/EA-SYS/EA-REPO/EA-BKP 编号族配置已随 seed 生效;移动资产扫码入口不回归。 受影响流程:管理员进入资产台账 -> 查询筛选 -> 移动扫码入口;验证步骤:公网 /ready;必要时使用浏览器或现有 smoke 检查 /assets 和 /mobile/assets/scan;核对部署输出和页面加载。 curl|ssh|node|manual DONE DONE DONE client/src/pages/AssetRegistryPage.tsx|client/src/pages/MobileAssetScanPage.tsx|server/src/scripts/seed-config.js ["ASSET1D-2"] 2026-05-23 复检:公网 /ready 返回 ok;public bundle 扫描 scanned=57 focused=4 findings=0;远端只读核验资产编号族 FA/EA-DOC/EA-SYS/EA-REPO/EA-BKP 均启用,number_length=6,reset_period=yearly;当前资产卡 total=0,ASSET1D-4 UI 试录仍待人工完成。 2026-05-23 复检:公网 /ready 返回 ok;public bundle 扫描 scanned=57 focused=4 findings=0;远端只读核验资产编号族 FA/EA-DOC/EA-SYS/EA-REPO/EA-BKP 均启用,number_length=6,reset_period=yearly;当前资产卡 total=0,ASSET1D-4 UI 试录仍待人工完成;回滚路径:基础核验失败则暂停试录并按部署回滚。
6 ASSET1D-4 最小资产 UI 试录 管理员在公网 OA 通过 UI 手工创建 1 条 FA、1 条 EA-SYS、1 条 EA-REPO、1 条 EA-BKP,并检查查看、编辑、筛选。 四类资产均可创建和查看;EA-SYS/EA-REPO/EA-BKP 不保存 Secret、源码、完整配置或完整路径;FA 可进入既有领用、归还、盘点链路。 受影响流程:管理员新建资产 -> 填元数据 -> 保存 -> 列表筛选 -> 详情查看;验证步骤:管理员登录公网 OA 手工创建 FA/EA-SYS/EA-REPO/EA-BKP 各 1 条并记录编号前缀。 受影响流程:管理员新建资产 -> 填元数据 -> 保存 -> 列表筛选 -> 详情查看;验证步骤:ASSET1D_REPORT_DIR=artifacts/qa-flow/asset1d-public-trial-20260523-155346 LIVE_SMOKE_BASE_URL=https://oa.hyinda.com bash scripts/qa/run-asset1d-public-trial.sh。 manual manual|playwright TODO DONE TODO DONE TODO DONE external|client/src/pages/AssetRegistryPage.tsx external|client/src/pages/AssetRegistryPage.tsx|client/tests/e2e/manual/asset1d-public-trial.spec.ts|scripts/qa/run-asset1d-public-trial.sh ["ASSET1D-3"] ["ASSET1D-3","ASSET1D-8"] Subagent 报告 artifacts/qa-flow/asset-ui-subagent-20260523T053741Z/summary.json 结论 BLOCKED:employee/manager 登录与入口检查通过,但 admin/ceo/finance/finance_mgr/hr smoke 账号均 403/AUTH_003;远端只读核验这些高权限 smoke 员工为 archived/inactive,无法执行资产 UI 试录;未创建资产。 公网 UI 试录通过;报告 artifacts/qa-flow/asset1d-public-trial-20260523-155346/summary.json;创建并保留审计:FA-2026-000001、EA-SYS-2026-000001、EA-REPO-2026-000001、EA-BKP-2026-000001;未写 EA-DOC,未上传文件、源码、Secret、完整配置或完整路径;回滚路径:如需清理,按测试标记归档或删除未被业务引用的试录资产。
7 ASSET1D-5 EA-DOC 历史索引脱敏 dry-run 从本地公司保密索引读取最多 3 条脱敏元数据,生成 dry-run 报告,不写公网 OA。 dry-run 报告状态为 PASS_REDACTED_INDEX_DRY_RUN_READY;报告仅含 code、docType、hash、敏感级别和脱敏要求,不含原文、完整路径、Secret 或账号凭据。 受影响流程:历史资料索引 -> 脱敏预览 -> 人工判断是否允许写入;验证步骤:node scripts/qa/collect-asset-redacted-index.mjs --limit=3。 node DONE DONE DONE scripts/qa/collect-asset-redacted-index.mjs|artifacts/qa-flow/asset-redacted-index-* ["ASSET1D-1"] 最新 dry-run 报告 artifacts/qa-flow/asset-redacted-index-20260523040650/summary.json;627 行中预览 3 行,均为 confidential;不含原文、完整路径、Secret 或账号凭据;未写 OA。
8 ASSET1D-6 EA-DOC 三条受控导入二次确认 UI 试录和 dry-run 通过后,再向用户确认是否开启 ASSET_INDEX_IMPORT_WRITE=1 写入 3 条脱敏 EA-DOC。 未获得二次确认前不得写入;若用户批准,必须使用进程级 ASSET_INDEX_IMPORT_WRITE=1、管理员权限、必填主数据,并验证审计日志。 受影响流程:脱敏预览 -> 人工批准 -> 受控导入 -> 审计核验;验证步骤:未批准时记录 PAUSED_MANUAL_CONFIRMATION;批准后调用 POST /api/assets/import/redacted-doc-index 并核对 imported/skipped 和审计。 manual|node|curl TODO TODO TODO server/src/routes/assets.js|external ["ASSET1D-4","ASSET1D-5"] 当前计划不自动执行 live 导入;回滚路径为按导入批次删除未被业务引用的 EA-DOC 台账或改为 archived。 当前未执行 live 导入;等待用户 A/B/C 二次确认;回滚路径为按导入批次删除未被业务引用的 EA-DOC 台账或改为 archived。
9 ASSET1D-7 ASSET1D 收口与提交 汇总本地门禁、部署结果、dry-run 报告、人工 UI 待办和二次确认状态,并用中文提交 Issue CSV 状态。 自动项完成并回填状态;人工项如未完成保持 TODO 或 PAUSED 说明;git commit 信息中文并引用 ASSET1D。 受影响流程:部署验收 -> 资产试录 -> 证据归档 -> 后续人工导入决策;验证步骤:git status;git diff --check;提交后 git status 干净。 git|manual DONE DONE DONE issues/2026-05-23_10-42-03-oa-asset-deploy-and-trial-v1.csv ["ASSET1D-2","ASSET1D-5"] 自动项已收口;ASSET1D-4 UI 试录和 ASSET1D-6 live 导入确认保持 TODO;本次提交只记录合同与事实状态,不伪造人工验收。 ASSET1D-4 公网 UI 试录已完成;ASSET1D-6 live 导入确认保持 TODO;本次提交记录合同与事实状态,不伪造 EA-DOC 写入;回滚路径:提交前发现阻断则保持 TODO 并不推进 EA-DOC 写入。
10 ASSET1D-8 资产创建 metadata 校验 500 修复 公网 ASSET1D-4 UI 试录保存资产时,POST /api/assets 因 Zod v4 record schema 写法不兼容返回 500,需要修复为参数校验可解析 metadata。 资产创建接口带 metadata 对象时不再 500;公网 UI 可继续试录 FA/EA-SYS/EA-REPO/EA-BKP;不改变权限模型或数据迁移。 受影响流程:管理员新建资产 -> 填写 metadata/保密说明 -> 保存 -> 列表回看;验证步骤:cd server && node --check src/routes/assets.js;cd server && npm run test -- tests/unit/routes/assets.validation.test.js;公网重跑 scripts/qa/run-asset1d-public-trial.sh。 node|vitest|playwright DONE DONE DONE server/src/routes/assets.js;server/tests/unit/routes/assets.validation.test.js ["ASSET1D-3"] 已将 metadata schema 修正为 Zod v4 兼容写法并部署;目标单测通过,公网 UI 试录复跑通过;初始失败 requestId 56d66b2c-02b5-4a99-8d8f-709c1d76b190;回滚路径:恢复 metadata schema 写法并将 ASSET1D-4 保持 blocked。
+50
查看文件
@@ -0,0 +1,50 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
CLIENT_DIR="$ROOT_DIR/client"
PUBLIC_BASE_URL="${LIVE_SMOKE_BASE_URL:-https://oa.hyinda.com}"
REPORT_DIR="${ASSET1D_REPORT_DIR:-$ROOT_DIR/artifacts/qa-flow/asset1d-public-trial-$(date +%Y%m%d-%H%M%S)}"
ENV_FILE="${ASSET1D_ENV_FILE:-$HOME/.hyb-oa/live-smoke.env}"
mkdir -p "$REPORT_DIR"
if [[ "${ASSET1D_SKIP_ENV_FILE:-0}" != "1" && -f "$ENV_FILE" ]]; then
set -a
# shellcheck disable=SC1090
source "$ENV_FILE"
set +a
PUBLIC_BASE_URL="${LIVE_SMOKE_BASE_URL:-$PUBLIC_BASE_URL}"
echo "[asset1d] loaded masked live smoke env file"
fi
if [[ -z "${LIVE_SMOKE_ADMIN_IDENTIFIER:-}" || -z "${LIVE_SMOKE_ADMIN_PASSWORD:-}" ]]; then
echo "[asset1d][ERROR] missing LIVE_SMOKE_ADMIN_IDENTIFIER/PASSWORD" >&2
exit 1
fi
echo "[asset1d] base=$PUBLIC_BASE_URL"
echo "[asset1d] report=$REPORT_DIR"
(
cd "$CLIENT_DIR"
PLAYWRIGHT_MANUAL=1 \
ASSET1D_PUBLIC_TRIAL=1 \
ASSET1D_REPORT_DIR="$REPORT_DIR" \
E2E_BASE_URL="$PUBLIC_BASE_URL" \
npx playwright test tests/e2e/manual/asset1d-public-trial.spec.ts --workers=1 --reporter=list
)
node - "$REPORT_DIR" <<'NODE'
const fs = require("node:fs");
const path = require("node:path");
const reportDir = process.argv[2];
const summaryPath = path.join(reportDir, "summary.json");
if (!fs.existsSync(summaryPath)) {
console.error(`[asset1d][ERROR] missing summary: ${summaryPath}`);
process.exit(1);
}
const summary = JSON.parse(fs.readFileSync(summaryPath, "utf8"));
console.log(`[asset1d] status=${summary.status} created=${summary.createdAssets?.length ?? 0}`);
if (summary.status !== "PASS_ASSET_UI_TRIAL_READY") process.exit(1);
NODE
+17 -12
查看文件
@@ -101,9 +101,22 @@ run_client_spec_if_present() {
return 0
fi
local cooldown="${OAOPS1_E2E_COOLDOWN_SECONDS:-2}"
if [[ "$cooldown" =~ ^[0-9]+$ && "$cooldown" -gt 0 ]]; then
sleep "$cooldown"
fi
run_client_finding_step "$name" "$@" npx playwright test "$spec" --workers=1 --reporter=list
}
cooldown_login_step() {
local reason="$1"
local cooldown="${OAOPS1_LOGIN_STEP_COOLDOWN_SECONDS:-65}"
if [[ "$cooldown" =~ ^[0-9]+$ && "$cooldown" -gt 0 ]]; then
log "[oaops1] login cooldown ${cooldown}s before ${reason}"
sleep "$cooldown"
fi
}
load_default_env() {
local env_file="${OAOPS1_ENV_FILE:-$HOME/.hyb-oa/live-smoke.env}"
if [[ "${OAOPS1_SKIP_ENV_FILE:-0}" == "1" || ! -f "$env_file" ]]; then
@@ -123,6 +136,7 @@ run_ready_and_runtime() {
run_finding_step "live-smoke-env" env LIVE_SMOKE_BASE_URL="$PUBLIC_BASE_URL" node "$ROOT_DIR/scripts/qa/check-live-smoke-env.mjs" --json
run_finding_step "live-readiness" env QA_ISSUE_ID=OAOPS1 LIVE_SMOKE_BASE_URL="$PUBLIC_BASE_URL" LIVE_SMOKE_READINESS_DIR="$REPORT_DIR/live-smoke-readiness" bash "$ROOT_DIR/scripts/qa/run-live-smoke-readiness.sh"
run_finding_step "oa-only-smoke-readiness" env LIVE_SMOKE_BASE_URL="$PUBLIC_BASE_URL" OA_ONLY_SMOKE_READINESS_REPORT_DIR="$REPORT_DIR/oa-only-smoke-readiness" node "$ROOT_DIR/scripts/qa/check-oa-only-smoke-readiness.mjs"
cooldown_login_step "live-feature-flags"
run_finding_step "live-feature-flags" env LIVE_SMOKE_BASE_URL="$PUBLIC_BASE_URL" node "$ROOT_DIR/scripts/qa/check-live-feature-flags.mjs" \
--expect payment_batch_v1=force_on \
--expect erp_sync=force_off \
@@ -144,14 +158,6 @@ const credentials = {
identifier: process.env.LIVE_SMOKE_ADMIN_IDENTIFIER?.trim(),
password: process.env.LIVE_SMOKE_ADMIN_PASSWORD?.trim(),
},
employee: {
identifier: process.env.LIVE_SMOKE_EMPLOYEE_IDENTIFIER?.trim(),
password: process.env.LIVE_SMOKE_EMPLOYEE_PASSWORD?.trim(),
},
finance: {
identifier: process.env.LIVE_SMOKE_FINANCE_IDENTIFIER?.trim(),
password: process.env.LIVE_SMOKE_FINANCE_PASSWORD?.trim(),
},
};
const probes = [
@@ -161,12 +167,9 @@ const probes = [
{ id: "reimbursement-summary", role: "admin", path: "/api/reimbursements/stats/summary", domain: "reimbursement" },
{ id: "invoice-pool", role: "admin", path: "/api/invoice-pool?page=1&pageSize=5", domain: "invoice_ocr" },
{ id: "purchase-requests", role: "admin", path: "/api/purchase-requests?page=1&pageSize=5", domain: "procurement" },
{ id: "contracts-registry", role: "admin", path: "/api/contracts-registry?page=1&pageSize=5", domain: "contracts" },
{ id: "contracts-registry", role: "admin", path: "/api/contracts-registry", domain: "contracts" },
{ id: "projects", role: "admin", path: "/api/projects?page=1&pageSize=5", domain: "projects" },
{ id: "payment-batches", role: "finance", path: "/api/payment-batches?page=1&pageSize=5", domain: "payment_batch" },
{ id: "ap-ledger", role: "finance", path: "/api/ap-operations/reimbursement-ledger?page=1&pageSize=5", domain: "accounting_handoff" },
{ id: "audit-logs", role: "admin", path: "/api/audit-logs?page=1&pageSize=5", domain: "audit" },
{ id: "employee-reimbursements", role: "employee", path: "/api/reimbursements?page=1&pageSize=5&mine=1", domain: "employee_scope" },
];
function maskIdentifier(value) {
@@ -281,6 +284,7 @@ async function main() {
baseUrl,
checkedAt: new Date().toISOString(),
readOnly: true,
duplicateRoleChecks: "employee/finance/payment/AP are covered by oa-only-smoke-readiness to avoid repeated login throttling",
roles: Object.fromEntries(Object.entries(roles).map(([role, state]) => [role, {
status: state.status,
reason: state.reason || null,
@@ -510,5 +514,6 @@ trap finish EXIT
load_default_env
run_ready_and_runtime
cooldown_login_step "authenticated-readonly-probe"
run_finding_step "authenticated-readonly-probe" run_authenticated_readonly_probe
run_mobile_and_entry_matrix
+4 -1
查看文件
@@ -81,7 +81,7 @@ const ASSET_CREATE_SCHEMA = z
sourceHash: z.string().trim().max(128).nullable().optional(),
verificationNo: z.string().trim().regex(/^VER-\d{5,}$/).max(32).nullable().optional(),
confidentialityNote: z.string().trim().max(1000).nullable().optional(),
metadata: z.record(z.unknown()).nullable().optional(),
metadata: z.record(z.string(), z.unknown()).nullable().optional(),
lastVerifiedAt: DATE_ONLY_SCHEMA.nullable().optional(),
expiresAt: DATE_ONLY_SCHEMA.nullable().optional(),
activatedAt: DATE_ONLY_SCHEMA.nullable().optional(),
@@ -1667,4 +1667,7 @@ async function assetsRoutes(fastify) {
module.exports = assetsRoutes;
module.exports.__testables = {
handleAssetLedgerSummaryErpSync,
parseAssetCreateBodyForTest(body) {
return normalizeAssetPayload(parseCreateBody(ASSET_CREATE_SCHEMA, body));
},
};
@@ -0,0 +1,31 @@
const assetsRoutes = require('../../../src/routes/assets');
describe('assets route validation', () => {
const validPayload = {
name: 'TEST SMOKE ASSET1D FA',
assetType: 'fixed_asset',
sensitivityLevel: 'internal',
categoryId: '11111111-1111-4111-8111-111111111111',
legalEntityId: '22222222-2222-4222-8222-222222222222',
costCenterId: '33333333-3333-4333-8333-333333333333',
ownerEmployeeId: '44444444-4444-4444-8444-444444444444',
keeperEmployeeId: '55555555-5555-4555-8555-555555555555',
metadata: {
testMarker: 'TEST-SMOKE-ASSET1D',
sourceOfTruth: 'git',
},
};
it('accepts ASSET1D metadata objects on asset create payloads', () => {
expect(assetsRoutes.__testables.parseAssetCreateBodyForTest(validPayload)).toEqual(
expect.objectContaining({
name: 'TEST SMOKE ASSET1D FA',
assetType: 'fixed_asset',
metadata: {
testMarker: 'TEST-SMOKE-ASSET1D',
sourceOfTruth: 'git',
},
}),
);
});
});