@things-factory/ops-master 10.1.206 → 10.1.209

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.
@@ -31,7 +31,7 @@ export interface MasterAxis {
31
31
  * 개정본을 결재로 보내는 것뿐이라, 그 길이 없는 축을 켜면 **그 축이 잠긴다**(2026-09-24). 그래서
32
32
  * 정책은 이 칸이 참인 축만 승인으로 켜게 한다(`declareMasterChangePolicy`).
33
33
  *
34
- * 앱이 정하지 않는다 — 개정본 문은 이 꾸러미가 지었고, 어느 앱에서도 없는 문으로는 보낼 수 없다.
34
+ * 앱이 정하지 않는다 — 개정본 문은 이 패키지가 지었고, 어느 앱에서도 없는 문으로는 보낼 수 없다.
35
35
  * 축마다 적게 한 것은, 새 축이 이 물음에 답하지 않고 들어오지 못하게 하려는 것이다.
36
36
  */
37
37
  revisable: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"master-axis.js","sourceRoot":"","sources":["../../server/service/master-axis.ts"],"names":[],"mappings":"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","sourcesContent":["import { ObjectLiteral } from 'typeorm'\n\nimport { Equipment } from './equipment/equipment'\nimport { EquipmentClass } from './equipment/equipment-class'\nimport { StopReason } from './equipment/stop-reason'\nimport { NonconformanceCause } from './quality/nonconformance-cause'\nimport { MaterialClass } from './material/material-class'\nimport { MaterialDefinition } from './material/material-definition'\nimport { OperationDefinition } from './operation/operation-definition'\nimport { OperationalLocation } from './location/operational-location'\nimport { Person } from './personnel/person'\nimport { PersonnelClass } from './personnel/personnel-class'\nimport { PhysicalAsset } from './asset/physical-asset'\nimport { PhysicalAssetClass } from './asset/physical-asset-class'\nimport { RecipeDefinition } from './recipe/recipe-definition'\nimport { RouteDefinition } from './recipe/route-definition'\nimport { TestSpecification } from './test-spec/test-specification'\n\n/**\n * 마스터 축의 **한 줄짜리 선언** — 축마다 다른 것만 적는다.\n *\n * ── 왜 표로 두나 ───────────────────────────────────────────────────────────\n * 축이 열다섯인데 조회와 저장은 모양이 같다. 축마다 리졸버를 통째로 베끼면 같은 코드가 열다섯 벌이 되고,\n * 규칙 하나를 고칠 때 열다섯 곳을 고쳐야 한다. 그러다 한 곳을 빠뜨리면 그 축만 다르게 동작하는데\n * 오류는 나지 않는다.\n *\n * 그래서 **다른 것만** 적는다 — 어느 표인가, 무엇으로 찾는가, 없앨 때 지우는가 끝을 채우는가.\n *\n * ── 왜 유효 기간 여부가 여기 있나 ──────────────────────────────────────────\n * 지난 기록이 가리키는 축은 지우면 안 된다. 그것이 축마다 다르므로 축의 성질로 적는다. 정지 사유는\n * 유효 기간이 있고, 등급도 있다. 지금 열다섯 모두 유효 기간을 갖는다 — 그래도 칸을 둔다. 없는 축이\n * 생기면 그때 이 표만 고치면 된다.\n */\n\nexport interface MasterAxis {\n /** GraphQL 이름의 바탕. `person` → `persons` · `updateMultiplePerson`. */\n key: string\n entity: { new (): ObjectLiteral }\n /** 검색어가 걸리는 칸들. */\n searchables: string[]\n /** 없앨 때 행을 지우지 않고 `effectiveEnd` 를 채우나. */\n hasEffectivePeriod: boolean\n /**\n * 이 축의 바뀜을 **개정본으로 결재에 보낼 수 있나.**\n *\n * 승인으로 켜면 `assertDirectMasterChange` 가 이 축의 직접 쓰기를 전부 거절한다. 그 뒤로 고치는 길은\n * 개정본을 결재로 보내는 것뿐이라, 그 길이 없는 축을 켜면 **그 축이 잠긴다**(2026-09-24). 그래서\n * 정책은 이 칸이 참인 축만 승인으로 켜게 한다(`declareMasterChangePolicy`).\n *\n * 앱이 정하지 않는다 — 개정본 문은 이 꾸러미가 지었고, 어느 앱에서도 없는 문으로는 보낼 수 없다.\n * 축마다 적게 한 것은, 새 축이 이 물음에 답하지 않고 들어오지 못하게 하려는 것이다.\n */\n revisable: boolean\n}\n\nexport const MASTER_AXES: readonly MasterAxis[] = [\n { key: 'personnelClass', entity: PersonnelClass, searchables: ['name', 'description'], hasEffectivePeriod: true, revisable: false },\n { key: 'person', entity: Person, searchables: ['name', 'description'], hasEffectivePeriod: true, revisable: false },\n { key: 'equipmentClass', entity: EquipmentClass, searchables: ['name', 'description'], hasEffectivePeriod: true, revisable: false },\n { key: 'equipment', entity: Equipment, searchables: ['name', 'description'], hasEffectivePeriod: true, revisable: false },\n { key: 'stopReason', entity: StopReason, searchables: ['name', 'description'], hasEffectivePeriod: true, revisable: false },\n { key: 'nonconformanceCause', entity: NonconformanceCause, searchables: ['name', 'description'], hasEffectivePeriod: true, revisable: false },\n { key: 'physicalAssetClass', entity: PhysicalAssetClass, searchables: ['name', 'description'], hasEffectivePeriod: true, revisable: false },\n { key: 'physicalAsset', entity: PhysicalAsset, searchables: ['name', 'description', 'identity'], hasEffectivePeriod: true, revisable: false },\n { key: 'materialClass', entity: MaterialClass, searchables: ['name', 'description'], hasEffectivePeriod: true, revisable: false },\n { key: 'materialDefinition', entity: MaterialDefinition, searchables: ['name', 'description'], hasEffectivePeriod: true, revisable: true },\n { key: 'operationalLocation', entity: OperationalLocation, searchables: ['name', 'description'], hasEffectivePeriod: true, revisable: false },\n { key: 'operationDefinition', entity: OperationDefinition, searchables: ['name', 'description'], hasEffectivePeriod: true, revisable: false },\n { key: 'testSpecification', entity: TestSpecification, searchables: ['name', 'description'], hasEffectivePeriod: true, revisable: true },\n /*\n * 라우팅과 레시피 (2026-08-31) — 커널에 `RouteDef`·`RecipeDef` 가 있고 `JobOrder.routeKey` 가 그것을\n * 가리키는데 **그 값이 가리킬 표가 없었다.** 사람이 문자열을 손으로 적는 상태였고, 그래서 MES 가 받은\n * 작업지시에 라우팅을 자동으로 붙일 수 없었다.\n */\n { key: 'routeDefinition', entity: RouteDefinition, searchables: ['name', 'description'], hasEffectivePeriod: true, revisable: false },\n {\n key: 'recipeDefinition',\n entity: RecipeDefinition,\n /* 만드는 품목으로도 찾는다 — 「이 품목을 만드는 레시피」가 MES 가 지시마다 묻는 물음이다. */\n searchables: ['name', 'description', 'materialDefinitionName'],\n hasEffectivePeriod: true,\n revisable: true\n }\n]\n\nexport function masterAxisOf(key: string): MasterAxis {\n const axis = MASTER_AXES.find(candidate => candidate.key === key)\n\n if (!axis) {\n /* 없는 축을 조용히 지나치지 않는다 — 지나치면 그 축의 화면이 빈 목록을 보이고 아무도 모른다. */\n throw new Error(`masterAxisOf: unknown master axis \"${key}\"`)\n }\n\n return axis\n}\n"]}
1
+ {"version":3,"file":"master-axis.js","sourceRoot":"","sources":["../../server/service/master-axis.ts"],"names":[],"mappings":"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","sourcesContent":["import { ObjectLiteral } from 'typeorm'\n\nimport { Equipment } from './equipment/equipment'\nimport { EquipmentClass } from './equipment/equipment-class'\nimport { StopReason } from './equipment/stop-reason'\nimport { NonconformanceCause } from './quality/nonconformance-cause'\nimport { MaterialClass } from './material/material-class'\nimport { MaterialDefinition } from './material/material-definition'\nimport { OperationDefinition } from './operation/operation-definition'\nimport { OperationalLocation } from './location/operational-location'\nimport { Person } from './personnel/person'\nimport { PersonnelClass } from './personnel/personnel-class'\nimport { PhysicalAsset } from './asset/physical-asset'\nimport { PhysicalAssetClass } from './asset/physical-asset-class'\nimport { RecipeDefinition } from './recipe/recipe-definition'\nimport { RouteDefinition } from './recipe/route-definition'\nimport { TestSpecification } from './test-spec/test-specification'\n\n/**\n * 마스터 축의 **한 줄짜리 선언** — 축마다 다른 것만 적는다.\n *\n * ── 왜 표로 두나 ───────────────────────────────────────────────────────────\n * 축이 열다섯인데 조회와 저장은 모양이 같다. 축마다 리졸버를 통째로 베끼면 같은 코드가 열다섯 벌이 되고,\n * 규칙 하나를 고칠 때 열다섯 곳을 고쳐야 한다. 그러다 한 곳을 빠뜨리면 그 축만 다르게 동작하는데\n * 오류는 나지 않는다.\n *\n * 그래서 **다른 것만** 적는다 — 어느 표인가, 무엇으로 찾는가, 없앨 때 지우는가 끝을 채우는가.\n *\n * ── 왜 유효 기간 여부가 여기 있나 ──────────────────────────────────────────\n * 지난 기록이 가리키는 축은 지우면 안 된다. 그것이 축마다 다르므로 축의 성질로 적는다. 정지 사유는\n * 유효 기간이 있고, 등급도 있다. 지금 열다섯 모두 유효 기간을 갖는다 — 그래도 칸을 둔다. 없는 축이\n * 생기면 그때 이 표만 고치면 된다.\n */\n\nexport interface MasterAxis {\n /** GraphQL 이름의 바탕. `person` → `persons` · `updateMultiplePerson`. */\n key: string\n entity: { new (): ObjectLiteral }\n /** 검색어가 걸리는 칸들. */\n searchables: string[]\n /** 없앨 때 행을 지우지 않고 `effectiveEnd` 를 채우나. */\n hasEffectivePeriod: boolean\n /**\n * 이 축의 바뀜을 **개정본으로 결재에 보낼 수 있나.**\n *\n * 승인으로 켜면 `assertDirectMasterChange` 가 이 축의 직접 쓰기를 전부 거절한다. 그 뒤로 고치는 길은\n * 개정본을 결재로 보내는 것뿐이라, 그 길이 없는 축을 켜면 **그 축이 잠긴다**(2026-09-24). 그래서\n * 정책은 이 칸이 참인 축만 승인으로 켜게 한다(`declareMasterChangePolicy`).\n *\n * 앱이 정하지 않는다 — 개정본 문은 이 패키지가 지었고, 어느 앱에서도 없는 문으로는 보낼 수 없다.\n * 축마다 적게 한 것은, 새 축이 이 물음에 답하지 않고 들어오지 못하게 하려는 것이다.\n */\n revisable: boolean\n}\n\nexport const MASTER_AXES: readonly MasterAxis[] = [\n { key: 'personnelClass', entity: PersonnelClass, searchables: ['name', 'description'], hasEffectivePeriod: true, revisable: false },\n { key: 'person', entity: Person, searchables: ['name', 'description'], hasEffectivePeriod: true, revisable: false },\n { key: 'equipmentClass', entity: EquipmentClass, searchables: ['name', 'description'], hasEffectivePeriod: true, revisable: false },\n { key: 'equipment', entity: Equipment, searchables: ['name', 'description'], hasEffectivePeriod: true, revisable: false },\n { key: 'stopReason', entity: StopReason, searchables: ['name', 'description'], hasEffectivePeriod: true, revisable: false },\n { key: 'nonconformanceCause', entity: NonconformanceCause, searchables: ['name', 'description'], hasEffectivePeriod: true, revisable: false },\n { key: 'physicalAssetClass', entity: PhysicalAssetClass, searchables: ['name', 'description'], hasEffectivePeriod: true, revisable: false },\n { key: 'physicalAsset', entity: PhysicalAsset, searchables: ['name', 'description', 'identity'], hasEffectivePeriod: true, revisable: false },\n { key: 'materialClass', entity: MaterialClass, searchables: ['name', 'description'], hasEffectivePeriod: true, revisable: false },\n { key: 'materialDefinition', entity: MaterialDefinition, searchables: ['name', 'description'], hasEffectivePeriod: true, revisable: true },\n { key: 'operationalLocation', entity: OperationalLocation, searchables: ['name', 'description'], hasEffectivePeriod: true, revisable: false },\n { key: 'operationDefinition', entity: OperationDefinition, searchables: ['name', 'description'], hasEffectivePeriod: true, revisable: false },\n { key: 'testSpecification', entity: TestSpecification, searchables: ['name', 'description'], hasEffectivePeriod: true, revisable: true },\n /*\n * 라우팅과 레시피 (2026-08-31) — 커널에 `RouteDef`·`RecipeDef` 가 있고 `JobOrder.routeKey` 가 그것을\n * 가리키는데 **그 값이 가리킬 표가 없었다.** 사람이 문자열을 손으로 적는 상태였고, 그래서 MES 가 받은\n * 작업지시에 라우팅을 자동으로 붙일 수 없었다.\n */\n { key: 'routeDefinition', entity: RouteDefinition, searchables: ['name', 'description'], hasEffectivePeriod: true, revisable: false },\n {\n key: 'recipeDefinition',\n entity: RecipeDefinition,\n /* 만드는 품목으로도 찾는다 — 「이 품목을 만드는 레시피」가 MES 가 지시마다 묻는 물음이다. */\n searchables: ['name', 'description', 'materialDefinitionName'],\n hasEffectivePeriod: true,\n revisable: true\n }\n]\n\nexport function masterAxisOf(key: string): MasterAxis {\n const axis = MASTER_AXES.find(candidate => candidate.key === key)\n\n if (!axis) {\n /* 없는 축을 조용히 지나치지 않는다 — 지나치면 그 축의 화면이 빈 목록을 보이고 아무도 모른다. */\n throw new Error(`masterAxisOf: unknown master axis \"${key}\"`)\n }\n\n return axis\n}\n"]}
@@ -10,7 +10,7 @@ export declare const POLICY_AXES: string[];
10
10
  /**
11
11
  * 이 축의 바뀜을 **개정본으로 결재에 보낼 수 있나** — 승인으로 켤 수 있는 축인가.
12
12
  *
13
- * 마스터 축은 자기 선언(`MasterAxis.revisable`)이 답한다. `workShift` 는 그 표 밖에서 자기 꾸러미가
13
+ * 마스터 축은 자기 선언(`MasterAxis.revisable`)이 답한다. `workShift` 는 그 표 밖에서 자기 패키지가
14
14
  * 개정 체계를 지었으므로 참이다(`work-shift-revision-service`). 앱이 답하지 않는다 — 문이 없으면
15
15
  * 어느 앱에서도 보낼 곳이 없다.
16
16
  */
@@ -29,7 +29,7 @@ exports.POLICY_AXES = [...master_axis_1.MASTER_AXES.map(one => one.key), 'workSh
29
29
  /**
30
30
  * 이 축의 바뀜을 **개정본으로 결재에 보낼 수 있나** — 승인으로 켤 수 있는 축인가.
31
31
  *
32
- * 마스터 축은 자기 선언(`MasterAxis.revisable`)이 답한다. `workShift` 는 그 표 밖에서 자기 꾸러미가
32
+ * 마스터 축은 자기 선언(`MasterAxis.revisable`)이 답한다. `workShift` 는 그 표 밖에서 자기 패키지가
33
33
  * 개정 체계를 지었으므로 참이다(`work-shift-revision-service`). 앱이 답하지 않는다 — 문이 없으면
34
34
  * 어느 앱에서도 보낼 곳이 없다.
35
35
  */
@@ -1 +1 @@
1
- {"version":3,"file":"master-change-policy.js","sourceRoot":"","sources":["../../server/service/master-change-policy.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Column,\n CreateDateColumn,\n Entity,\n EntityManager,\n Index,\n ManyToOne,\n PrimaryGeneratedColumn,\n RelationId\n} from 'typeorm'\nimport { IsNull } from 'typeorm'\nimport { Arg, Ctx, Directive, Mutation, Query, Resolver, UseMiddleware } from 'type-graphql'\nimport { Domain, getRepository, Refusal, ScalarObject, SpeakRefusals } from '@things-factory/shell'\nimport { User } from '@things-factory/auth-base'\nimport { MASTER_AXES, masterAxisOf } from './master-axis'\nimport { assertApprovalRouteReady, inspectApprovalRoute } from './approval-route'\n\n// WorkShift owns a domain-wide arrangement, not a row in the generic master CRUD registry.\nfunction policyAxis(axis: string) {\n if (axis !== 'workShift') masterAxisOf(axis)\n}\n\n/**\n * 정책을 정할 수 있는 축들 — 마스터 축 열다섯에 `workShift` 를 더한다.\n *\n * `workShift` 는 별도 모듈이라 `MASTER_AXES` 에 없는데 정책은 받는다(§`policyAxis`).\n */\nexport const POLICY_AXES = [...MASTER_AXES.map(one => one.key), 'workShift']\n\n/**\n * 이 축의 바뀜을 **개정본으로 결재에 보낼 수 있나** — 승인으로 켤 수 있는 축인가.\n *\n * 마스터 축은 자기 선언(`MasterAxis.revisable`)이 답한다. `workShift` 는 그 표 밖에서 자기 꾸러미가\n * 개정 체계를 지었으므로 참이다(`work-shift-revision-service`). 앱이 답하지 않는다 — 문이 없으면\n * 어느 앱에서도 보낼 곳이 없다.\n */\nexport function axisHasRevision(axis: string): boolean {\n if (axis === 'workShift') return true\n return masterAxisOf(axis).revisable\n}\n\n/** 도메인 × 축의 기준정보 변경 정책 — 정하는 것도 바꾸는 것도 사업장 소유자의 몫이다. */\n@Entity('ops_master_change_policy')\n/*\n * 도메인 × 축에 **줄이 여럿 선다.** 정책은 바뀌고, 바뀌면 새 줄이 서고, 지난 개정본은 자기가 만들어질\n * 때의 줄을 그대로 가리킨다 — 그래야 「그때는 무슨 규칙이었나」가 안 흔들린다.\n *\n * 전에는 유니크였다. 그래서 바꾸려면 그 줄을 덮어야 했고, 덮으면 승인받고 만든 개정본이 소급해서\n * 승인 없이 만든 것이 됐다. 그래서 코드가 바꾸기를 거절했고, **정책을 영영 못 바꿨다.**\n *\n * ⚠ **이 정책을 바꾸는 일 자체는 결재에 안 태운다**(2026-09-22). 사업장 소유자가 정하는 것이라\n * 소유자 확인만 받고 바로 선다. 태울 필요가 생기면 그때 결재 활동을 붙인다 — 줄이 여럿 서는\n * 구조라 붙이는 자리는 이미 있다.\n */\n@Index('ix_master_change_policy_axis', ['domain', 'axis'])\n@Index('ix_master_change_policy_identity', ['domain', 'id'], { unique: true })\nexport class MasterChangePolicy {\n @PrimaryGeneratedColumn('uuid') id: string\n @ManyToOne(() => Domain, { nullable: false }) domain: Domain\n @RelationId((row: MasterChangePolicy) => row.domain) domainId: string\n @Column() axis: string\n @Column() approvalRequired: boolean\n @Column('text') reason: string\n /**\n * 이 줄이 물러난 시각 — 비어 있는 줄이 **지금 정책**이다.\n *\n * 차례를 `createdAt` 으로 세우지 않는다. sqlite 의 `datetime('now')` 는 초까지라, 한 사람이 같은\n * 초에 두 번 바꾸면 어느 줄이 나중인지 저장소가 못 가린다. 물러난 줄에 표를 하면 그 물음이 사라진다.\n */\n @Column({ nullable: true }) supersededAt: Date\n /**\n * 누가 정했나 — **비어 있으면 아무도 안 정한 것**이고, 그 줄은 기본값으로 선 줄이다.\n *\n * 사람이 안 정한 줄에 사람을 적으면 그 사람이 정한 것이 된다. 사업장을 만든 사람도 이 축을 보고\n * 고른 적이 없다.\n */\n @ManyToOne(() => User, { nullable: true }) creator: User\n @RelationId((row: MasterChangePolicy) => row.creator) creatorId: string\n @CreateDateColumn() createdAt: Date\n}\n\n/** 아무도 안 정한 축이 도는 방식 — 바꾸려면 여기 하나만 고치면 된다. */\nexport const DEFAULT_APPROVAL_REQUIRED = false\n\n/**\n * 이 축이 지금 어떻게 도는가. **아무도 안 정했으면 기본값으로 한 줄을 세우고 그것을 답한다.**\n *\n * 전에는 거절했다(`MASTER_POLICY_REQUIRED`). 그래서 새 사업장은 축 열여섯이 전부 막힌 채로 시작했고,\n * 기준정보를 만들려던 사람이 「정책을 먼저 정하라」는 말만 받았다 — 어디서 정하는지는 안 적혀 있었다.\n *\n * 값을 그때그때 지어내지 않고 **줄을 세우는** 까닭은, 개정본이 자기가 어느 정책 아래 만들어졌는지를\n * 그 줄로 가리키기 때문이다(ADR-0090). 가리킬 줄이 없으면 그 물음에 나중에 답할 수 없다.\n *\n * 그 줄의 `creator` 는 비어 있다. 아무도 고른 적이 없다는 것이 사실이고, 사업장을 만든 사람을 적으면\n * 그 사람이 정한 것이 된다.\n */\nexport async function masterChangePolicy(manager: EntityManager, domainId: string, axis: string) {\n policyAxis(axis)\n const policy = await standingPolicy(manager, domainId, axis)\n if (policy) {\n return policy\n }\n\n if (!manager.queryRunner?.isTransactionActive)\n throw new Refusal('TRANSACTION_REQUIRED', {}, 'A transaction is required.')\n\n return manager.save(\n MasterChangePolicy,\n manager.create(MasterChangePolicy, {\n domain: { id: domainId },\n axis,\n approvalRequired: DEFAULT_APPROVAL_REQUIRED,\n reason: 'nobody set this axis; it runs on the default'\n })\n )\n}\n\n/** 이 축에서 물러나지 않은 줄 전부 — 보통 하나다. */\nfunction standingRows(manager: EntityManager, domainId: string, axis: string) {\n return manager.findBy(MasterChangePolicy, { domain: { id: domainId }, axis, supersededAt: IsNull() })\n}\n\n/**\n * 지금 서 있는 줄.\n *\n * 둘 이상이 서 있으면 **가장 늦게 만들어진 것**을 답한다. 그 상태는 이 코드가 못 만들지만, 물러남\n * 표를 두기 전에 쌓인 줄들이 그렇게 남아 있다. 다음 선언이 전부 물러나게 하고 하나로 정리한다.\n */\nexport async function standingPolicy(manager: EntityManager, domainId: string, axis: string) {\n const rows = await standingRows(manager, domainId, axis)\n\n return rows.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime()).pop() ?? null\n}\n\n/** 이 도메인에서 지금 서 있는 줄 전부 — 축을 열쇠로. */\nexport async function standingPolicies(manager: EntityManager, domainId: string) {\n const rows = await manager.findBy(MasterChangePolicy, { domain: { id: domainId }, supersededAt: IsNull() })\n\n /* 이른 것부터 담아서 같은 축은 늦은 것이 이긴다 — `standingPolicy` 와 같은 답이 되도록. */\n rows.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime())\n\n return new Map(rows.map(one => [one.axis, one]))\n}\n\nexport async function declareMasterChangePolicy(\n manager: EntityManager,\n domainId: string,\n actorId: string,\n axis: string,\n approvalRequired: boolean,\n reason: string\n) {\n if (!manager.queryRunner?.isTransactionActive)\n throw new Refusal('TRANSACTION_REQUIRED', {}, 'A transaction is required.')\n policyAxis(axis)\n const domain = await manager.findOneByOrFail(Domain, { id: domainId })\n if (domain.owner !== actorId)\n throw new Refusal(\n 'MASTER_POLICY_OWNER_REQUIRED',\n {},\n 'Only the domain owner may declare or change the master change policy.'\n )\n if (!reason?.trim()) throw new Refusal('MASTER_POLICY_REASON_REQUIRED', {}, 'Explain the policy declaration.')\n\n /*\n * 규제를 **거는** 쪽만 본다. 푸는 쪽은 결재선이 비어 있어도 되고, 오히려 비어서 멈춘 축을 푸는 것이\n * 이 화면으로 할 수 있는 일이어야 한다.\n */\n if (approvalRequired) {\n /*\n * 개정본 문이 없는 축은 **켜는 것부터 막는다**(2026-09-24). 켜면 직접 쓰기가 전부 거절되는데 결재로\n * 보낼 곳이 없어서 그 축이 통째로 잠긴다. 결재선을 묻기 전에 본다 — 결재선이 있어도 보낼 문이 없다.\n */\n if (!axisHasRevision(axis))\n throw new Refusal(\n 'MASTER_AXIS_HAS_NO_REVISION',\n { axis },\n 'This master cannot be sent for approval yet, so it cannot be set to change only after approval.'\n )\n await assertApprovalRouteReady(manager, domainId, axis)\n }\n\n const standing = await standingRows(manager, domainId, axis)\n\n /*\n * 같은 값을 다시 정하는 것은 아무 일도 아니다 — **하나만 서 있을 때** 그렇다. 여럿이 서 있으면\n * 어느 것이 지금 것인지가 안 정해진 상태이므로, 값이 같아 보여도 전부 물리고 하나로 다시 세운다.\n */\n if (standing.length === 1 && standing[0].approvalRequired === approvalRequired) {\n return standing[0]\n }\n\n const supersededAt = new Date()\n for (const row of standing) {\n row.supersededAt = supersededAt\n await manager.save(MasterChangePolicy, row)\n }\n\n /*\n * 바뀌면 **새 줄을 세운다. 있던 줄은 그대로 둔다.**\n *\n * 지난 개정본들이 그 줄을 가리키고 있다(`RecipeRevision.policy`, RESTRICT). 덮으면 그것들이\n * 소급해서 다른 규칙 아래 만들어진 것이 된다 — 그래서 덮지 않고 새로 세운다. 오늘부터는 새 줄이\n * 현재 정책이고, 지난 것은 자기 때의 줄을 계속 가리킨다.\n */\n return manager.save(\n MasterChangePolicy,\n manager.create(MasterChangePolicy, {\n domain: { id: domainId },\n axis,\n approvalRequired,\n reason,\n creator: { id: actorId }\n })\n )\n}\n\nexport async function assertDirectMasterChange(manager: EntityManager, domainId: string, axis: string) {\n const policy = await standingPolicy(manager, domainId, axis)\n if (policy?.approvalRequired)\n throw new Refusal('MASTER_APPROVAL_REQUIRED', { axis }, 'This master axis requires approval in this domain.')\n}\n\n@Resolver()\n@UseMiddleware(SpeakRefusals)\nexport class MasterChangePolicyResolver {\n @Directive('@privilege(category: \"ops-master\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(() => ScalarObject)\n async masterChangePolicy(@Arg('axis') axis: string, @Ctx() context: ResolverContext) {\n /* 읽기만 하는 자리다. 안 정한 축에 줄을 세우지 않는다 — 그것은 고치러 들어간 쪽이 할 일이다. */\n policyAxis(axis)\n const policy = await standingPolicy(getRepository(MasterChangePolicy).manager, context.state.domain.id, axis)\n // A fresh site's editor must see the same default the first write will use, without inventing a policy id.\n return policy\n ? { ...policy, declared: true }\n : { axis, declared: false, approvalRequired: DEFAULT_APPROVAL_REQUIRED }\n }\n\n /**\n * 축 전부의 정책 상태 — **안 정한 것도 답한다.**\n *\n * 단일 조회처럼 안 정한 축은 기본값과 미선언 상태를 답한다. 목록에는 관리 권한과 결재 경로 상태도 담는다.\n *\n * `canDeclare` 는 **이 사람이 소유자인가**다. 화면이 스스로 판단하지 않게 서버가 말한다 —\n * 눌러 보고 거절당하는 화면이 되지 않도록.\n */\n @Directive('@privilege(category: \"ops-master\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(() => ScalarObject)\n async masterChangePolicies(@Ctx() context: ResolverContext) {\n const { domain, user } = context.state\n const manager = getRepository(MasterChangePolicy).manager\n /*\n * **물러나지 않은 줄만** 읽는다. 축마다 줄이 여럿이라, 전부 담으면 저장소가 주는 차례대로 덮여서\n * 바꾼 뒤에도 화면에 옛 값이 떴다(2026-09-22 화면에서 확인).\n */\n const byAxis = await standingPolicies(manager, domain.id)\n\n return {\n canDeclare: domain.owner === user?.id,\n axes: await Promise.all(\n POLICY_AXES.map(async axis => {\n const policy = byAxis.get(axis)\n /*\n * 결재 길이 준비됐나 — **화면이 누르기 전에 알아야 한다.** 답이 없는 축은 이 앱이 아직 이\n * 물음에 답할 길이 없다는 뜻이라 막지 않는다(`inspectApprovalRoute`).\n */\n const route = await inspectApprovalRoute(manager, domain.id, axis)\n\n return {\n axis,\n declared: !!policy,\n /* 줄은 섰지만 사람이 고른 적은 없다 — 화면이 그것을 다르게 보인다. */\n byDefault: !!policy && !policy.creatorId,\n approvalRequired: policy?.approvalRequired ?? null,\n reason: policy?.reason ?? null,\n declaredAt: policy?.createdAt ?? null,\n /* 승인으로 켤 수 있는 축인가 — 아니면 화면은 누르기 전에 그 까닭을 말한다. */\n revisable: axisHasRevision(axis),\n approvalReady: route ? route.ready : true,\n approvalActivity: route?.activity ?? null\n }\n })\n )\n }\n }\n\n @Directive('@privilege(category: \"ops-master\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(() => ScalarObject)\n async declareMasterChangePolicy(\n @Arg('axis') axis: string,\n @Arg('approvalRequired') approvalRequired: boolean,\n @Arg('reason') reason: string,\n @Ctx() context: ResolverContext\n ) {\n const { tx, domain, user } = context.state\n return declareMasterChangePolicy(tx, domain.id, user.id, axis, approvalRequired, reason)\n }\n}\n"]}
1
+ {"version":3,"file":"master-change-policy.js","sourceRoot":"","sources":["../../server/service/master-change-policy.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Column,\n CreateDateColumn,\n Entity,\n EntityManager,\n Index,\n ManyToOne,\n PrimaryGeneratedColumn,\n RelationId\n} from 'typeorm'\nimport { IsNull } from 'typeorm'\nimport { Arg, Ctx, Directive, Mutation, Query, Resolver, UseMiddleware } from 'type-graphql'\nimport { Domain, getRepository, Refusal, ScalarObject, SpeakRefusals } from '@things-factory/shell'\nimport { User } from '@things-factory/auth-base'\nimport { MASTER_AXES, masterAxisOf } from './master-axis'\nimport { assertApprovalRouteReady, inspectApprovalRoute } from './approval-route'\n\n// WorkShift owns a domain-wide arrangement, not a row in the generic master CRUD registry.\nfunction policyAxis(axis: string) {\n if (axis !== 'workShift') masterAxisOf(axis)\n}\n\n/**\n * 정책을 정할 수 있는 축들 — 마스터 축 열다섯에 `workShift` 를 더한다.\n *\n * `workShift` 는 별도 모듈이라 `MASTER_AXES` 에 없는데 정책은 받는다(§`policyAxis`).\n */\nexport const POLICY_AXES = [...MASTER_AXES.map(one => one.key), 'workShift']\n\n/**\n * 이 축의 바뀜을 **개정본으로 결재에 보낼 수 있나** — 승인으로 켤 수 있는 축인가.\n *\n * 마스터 축은 자기 선언(`MasterAxis.revisable`)이 답한다. `workShift` 는 그 표 밖에서 자기 패키지가\n * 개정 체계를 지었으므로 참이다(`work-shift-revision-service`). 앱이 답하지 않는다 — 문이 없으면\n * 어느 앱에서도 보낼 곳이 없다.\n */\nexport function axisHasRevision(axis: string): boolean {\n if (axis === 'workShift') return true\n return masterAxisOf(axis).revisable\n}\n\n/** 도메인 × 축의 기준정보 변경 정책 — 정하는 것도 바꾸는 것도 사업장 소유자의 몫이다. */\n@Entity('ops_master_change_policy')\n/*\n * 도메인 × 축에 **줄이 여럿 선다.** 정책은 바뀌고, 바뀌면 새 줄이 서고, 지난 개정본은 자기가 만들어질\n * 때의 줄을 그대로 가리킨다 — 그래야 「그때는 무슨 규칙이었나」가 안 흔들린다.\n *\n * 전에는 유니크였다. 그래서 바꾸려면 그 줄을 덮어야 했고, 덮으면 승인받고 만든 개정본이 소급해서\n * 승인 없이 만든 것이 됐다. 그래서 코드가 바꾸기를 거절했고, **정책을 영영 못 바꿨다.**\n *\n * ⚠ **이 정책을 바꾸는 일 자체는 결재에 안 태운다**(2026-09-22). 사업장 소유자가 정하는 것이라\n * 소유자 확인만 받고 바로 선다. 태울 필요가 생기면 그때 결재 활동을 붙인다 — 줄이 여럿 서는\n * 구조라 붙이는 자리는 이미 있다.\n */\n@Index('ix_master_change_policy_axis', ['domain', 'axis'])\n@Index('ix_master_change_policy_identity', ['domain', 'id'], { unique: true })\nexport class MasterChangePolicy {\n @PrimaryGeneratedColumn('uuid') id: string\n @ManyToOne(() => Domain, { nullable: false }) domain: Domain\n @RelationId((row: MasterChangePolicy) => row.domain) domainId: string\n @Column() axis: string\n @Column() approvalRequired: boolean\n @Column('text') reason: string\n /**\n * 이 줄이 물러난 시각 — 비어 있는 줄이 **지금 정책**이다.\n *\n * 차례를 `createdAt` 으로 세우지 않는다. sqlite 의 `datetime('now')` 는 초까지라, 한 사람이 같은\n * 초에 두 번 바꾸면 어느 줄이 나중인지 저장소가 못 가린다. 물러난 줄에 표를 하면 그 물음이 사라진다.\n */\n @Column({ nullable: true }) supersededAt: Date\n /**\n * 누가 정했나 — **비어 있으면 아무도 안 정한 것**이고, 그 줄은 기본값으로 선 줄이다.\n *\n * 사람이 안 정한 줄에 사람을 적으면 그 사람이 정한 것이 된다. 사업장을 만든 사람도 이 축을 보고\n * 고른 적이 없다.\n */\n @ManyToOne(() => User, { nullable: true }) creator: User\n @RelationId((row: MasterChangePolicy) => row.creator) creatorId: string\n @CreateDateColumn() createdAt: Date\n}\n\n/** 아무도 안 정한 축이 도는 방식 — 바꾸려면 여기 하나만 고치면 된다. */\nexport const DEFAULT_APPROVAL_REQUIRED = false\n\n/**\n * 이 축이 지금 어떻게 도는가. **아무도 안 정했으면 기본값으로 한 줄을 세우고 그것을 답한다.**\n *\n * 전에는 거절했다(`MASTER_POLICY_REQUIRED`). 그래서 새 사업장은 축 열여섯이 전부 막힌 채로 시작했고,\n * 기준정보를 만들려던 사람이 「정책을 먼저 정하라」는 말만 받았다 — 어디서 정하는지는 안 적혀 있었다.\n *\n * 값을 그때그때 지어내지 않고 **줄을 세우는** 까닭은, 개정본이 자기가 어느 정책 아래 만들어졌는지를\n * 그 줄로 가리키기 때문이다(ADR-0090). 가리킬 줄이 없으면 그 물음에 나중에 답할 수 없다.\n *\n * 그 줄의 `creator` 는 비어 있다. 아무도 고른 적이 없다는 것이 사실이고, 사업장을 만든 사람을 적으면\n * 그 사람이 정한 것이 된다.\n */\nexport async function masterChangePolicy(manager: EntityManager, domainId: string, axis: string) {\n policyAxis(axis)\n const policy = await standingPolicy(manager, domainId, axis)\n if (policy) {\n return policy\n }\n\n if (!manager.queryRunner?.isTransactionActive)\n throw new Refusal('TRANSACTION_REQUIRED', {}, 'A transaction is required.')\n\n return manager.save(\n MasterChangePolicy,\n manager.create(MasterChangePolicy, {\n domain: { id: domainId },\n axis,\n approvalRequired: DEFAULT_APPROVAL_REQUIRED,\n reason: 'nobody set this axis; it runs on the default'\n })\n )\n}\n\n/** 이 축에서 물러나지 않은 줄 전부 — 보통 하나다. */\nfunction standingRows(manager: EntityManager, domainId: string, axis: string) {\n return manager.findBy(MasterChangePolicy, { domain: { id: domainId }, axis, supersededAt: IsNull() })\n}\n\n/**\n * 지금 서 있는 줄.\n *\n * 둘 이상이 서 있으면 **가장 늦게 만들어진 것**을 답한다. 그 상태는 이 코드가 못 만들지만, 물러남\n * 표를 두기 전에 쌓인 줄들이 그렇게 남아 있다. 다음 선언이 전부 물러나게 하고 하나로 정리한다.\n */\nexport async function standingPolicy(manager: EntityManager, domainId: string, axis: string) {\n const rows = await standingRows(manager, domainId, axis)\n\n return rows.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime()).pop() ?? null\n}\n\n/** 이 도메인에서 지금 서 있는 줄 전부 — 축을 열쇠로. */\nexport async function standingPolicies(manager: EntityManager, domainId: string) {\n const rows = await manager.findBy(MasterChangePolicy, { domain: { id: domainId }, supersededAt: IsNull() })\n\n /* 이른 것부터 담아서 같은 축은 늦은 것이 이긴다 — `standingPolicy` 와 같은 답이 되도록. */\n rows.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime())\n\n return new Map(rows.map(one => [one.axis, one]))\n}\n\nexport async function declareMasterChangePolicy(\n manager: EntityManager,\n domainId: string,\n actorId: string,\n axis: string,\n approvalRequired: boolean,\n reason: string\n) {\n if (!manager.queryRunner?.isTransactionActive)\n throw new Refusal('TRANSACTION_REQUIRED', {}, 'A transaction is required.')\n policyAxis(axis)\n const domain = await manager.findOneByOrFail(Domain, { id: domainId })\n if (domain.owner !== actorId)\n throw new Refusal(\n 'MASTER_POLICY_OWNER_REQUIRED',\n {},\n 'Only the domain owner may declare or change the master change policy.'\n )\n if (!reason?.trim()) throw new Refusal('MASTER_POLICY_REASON_REQUIRED', {}, 'Explain the policy declaration.')\n\n /*\n * 규제를 **거는** 쪽만 본다. 푸는 쪽은 결재선이 비어 있어도 되고, 오히려 비어서 멈춘 축을 푸는 것이\n * 이 화면으로 할 수 있는 일이어야 한다.\n */\n if (approvalRequired) {\n /*\n * 개정본 문이 없는 축은 **켜는 것부터 막는다**(2026-09-24). 켜면 직접 쓰기가 전부 거절되는데 결재로\n * 보낼 곳이 없어서 그 축이 통째로 잠긴다. 결재선을 묻기 전에 본다 — 결재선이 있어도 보낼 문이 없다.\n */\n if (!axisHasRevision(axis))\n throw new Refusal(\n 'MASTER_AXIS_HAS_NO_REVISION',\n { axis },\n 'This master cannot be sent for approval yet, so it cannot be set to change only after approval.'\n )\n await assertApprovalRouteReady(manager, domainId, axis)\n }\n\n const standing = await standingRows(manager, domainId, axis)\n\n /*\n * 같은 값을 다시 정하는 것은 아무 일도 아니다 — **하나만 서 있을 때** 그렇다. 여럿이 서 있으면\n * 어느 것이 지금 것인지가 안 정해진 상태이므로, 값이 같아 보여도 전부 물리고 하나로 다시 세운다.\n */\n if (standing.length === 1 && standing[0].approvalRequired === approvalRequired) {\n return standing[0]\n }\n\n const supersededAt = new Date()\n for (const row of standing) {\n row.supersededAt = supersededAt\n await manager.save(MasterChangePolicy, row)\n }\n\n /*\n * 바뀌면 **새 줄을 세운다. 있던 줄은 그대로 둔다.**\n *\n * 지난 개정본들이 그 줄을 가리키고 있다(`RecipeRevision.policy`, RESTRICT). 덮으면 그것들이\n * 소급해서 다른 규칙 아래 만들어진 것이 된다 — 그래서 덮지 않고 새로 세운다. 오늘부터는 새 줄이\n * 현재 정책이고, 지난 것은 자기 때의 줄을 계속 가리킨다.\n */\n return manager.save(\n MasterChangePolicy,\n manager.create(MasterChangePolicy, {\n domain: { id: domainId },\n axis,\n approvalRequired,\n reason,\n creator: { id: actorId }\n })\n )\n}\n\nexport async function assertDirectMasterChange(manager: EntityManager, domainId: string, axis: string) {\n const policy = await standingPolicy(manager, domainId, axis)\n if (policy?.approvalRequired)\n throw new Refusal('MASTER_APPROVAL_REQUIRED', { axis }, 'This master axis requires approval in this domain.')\n}\n\n@Resolver()\n@UseMiddleware(SpeakRefusals)\nexport class MasterChangePolicyResolver {\n @Directive('@privilege(category: \"ops-master\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(() => ScalarObject)\n async masterChangePolicy(@Arg('axis') axis: string, @Ctx() context: ResolverContext) {\n /* 읽기만 하는 자리다. 안 정한 축에 줄을 세우지 않는다 — 그것은 고치러 들어간 쪽이 할 일이다. */\n policyAxis(axis)\n const policy = await standingPolicy(getRepository(MasterChangePolicy).manager, context.state.domain.id, axis)\n // A fresh site's editor must see the same default the first write will use, without inventing a policy id.\n return policy\n ? { ...policy, declared: true }\n : { axis, declared: false, approvalRequired: DEFAULT_APPROVAL_REQUIRED }\n }\n\n /**\n * 축 전부의 정책 상태 — **안 정한 것도 답한다.**\n *\n * 단일 조회처럼 안 정한 축은 기본값과 미선언 상태를 답한다. 목록에는 관리 권한과 결재 경로 상태도 담는다.\n *\n * `canDeclare` 는 **이 사람이 소유자인가**다. 화면이 스스로 판단하지 않게 서버가 말한다 —\n * 눌러 보고 거절당하는 화면이 되지 않도록.\n */\n @Directive('@privilege(category: \"ops-master\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(() => ScalarObject)\n async masterChangePolicies(@Ctx() context: ResolverContext) {\n const { domain, user } = context.state\n const manager = getRepository(MasterChangePolicy).manager\n /*\n * **물러나지 않은 줄만** 읽는다. 축마다 줄이 여럿이라, 전부 담으면 저장소가 주는 차례대로 덮여서\n * 바꾼 뒤에도 화면에 옛 값이 떴다(2026-09-22 화면에서 확인).\n */\n const byAxis = await standingPolicies(manager, domain.id)\n\n return {\n canDeclare: domain.owner === user?.id,\n axes: await Promise.all(\n POLICY_AXES.map(async axis => {\n const policy = byAxis.get(axis)\n /*\n * 결재 길이 준비됐나 — **화면이 누르기 전에 알아야 한다.** 답이 없는 축은 이 앱이 아직 이\n * 물음에 답할 길이 없다는 뜻이라 막지 않는다(`inspectApprovalRoute`).\n */\n const route = await inspectApprovalRoute(manager, domain.id, axis)\n\n return {\n axis,\n declared: !!policy,\n /* 줄은 섰지만 사람이 고른 적은 없다 — 화면이 그것을 다르게 보인다. */\n byDefault: !!policy && !policy.creatorId,\n approvalRequired: policy?.approvalRequired ?? null,\n reason: policy?.reason ?? null,\n declaredAt: policy?.createdAt ?? null,\n /* 승인으로 켤 수 있는 축인가 — 아니면 화면은 누르기 전에 그 까닭을 말한다. */\n revisable: axisHasRevision(axis),\n approvalReady: route ? route.ready : true,\n approvalActivity: route?.activity ?? null\n }\n })\n )\n }\n }\n\n @Directive('@privilege(category: \"ops-master\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(() => ScalarObject)\n async declareMasterChangePolicy(\n @Arg('axis') axis: string,\n @Arg('approvalRequired') approvalRequired: boolean,\n @Arg('reason') reason: string,\n @Ctx() context: ResolverContext\n ) {\n const { tx, domain, user } = context.state\n return declareMasterChangePolicy(tx, domain.id, user.id, axis, approvalRequired, reason)\n }\n}\n"]}
@@ -27,6 +27,7 @@ const material_class_1 = require("./material-class");
27
27
  const test_specification_1 = require("../test-spec/test-specification");
28
28
  const material_revision_1 = require("./material-revision");
29
29
  const approval_input_1 = require("../master-change-approval/approval-input");
30
+ const row_lock_1 = require("../row-lock");
30
31
  class MaterialDefinitionRefusal extends shell_1.Refusal {
31
32
  constructor(code, detail) {
32
33
  super(code, {}, detail ?? MATERIAL_DEFINITION_REFUSALS[code] ?? 'The material definition change could not be applied.');
@@ -62,10 +63,6 @@ function transaction(manager) {
62
63
  if (!manager.queryRunner?.isTransactionActive)
63
64
  refuse('TRANSACTION_REQUIRED');
64
65
  }
65
- /** Current reads are required after waiting for the subject lock, including MySQL REPEATABLE READ. */
66
- function currentLock(manager) {
67
- return (0, shell_1.writeLockSupported)(manager) ? { lock: { mode: 'pessimistic_write' } } : {};
68
- }
69
66
  function instant(value) {
70
67
  if (!(value instanceof Date) && (typeof value !== 'string' || !/(Z|[+-]\d{2}:\d{2})$/.test(value)))
71
68
  refuse('INVALID_INSTANT');
@@ -171,7 +168,7 @@ function content(input) {
171
168
  async function subject(manager, domainId, materialId) {
172
169
  const material = await manager.findOne(material_definition_1.MaterialDefinition, {
173
170
  where: { id: materialId, domain: { id: domainId } },
174
- ...currentLock(manager)
171
+ ...(0, row_lock_1.writeLockOn)(manager, material_definition_1.MaterialDefinition)
175
172
  });
176
173
  if (!material)
177
174
  refuse('MATERIAL_NOT_FOUND');
@@ -192,7 +189,7 @@ async function subject(manager, domainId, materialId) {
192
189
  async function revision(manager, domainId, revisionId, locked = true) {
193
190
  const found = await manager.findOne(material_revision_1.MaterialDefinitionRevision, {
194
191
  where: { id: revisionId, domain: { id: domainId } },
195
- ...(locked ? currentLock(manager) : {})
192
+ ...(locked ? (0, row_lock_1.writeLockOn)(manager, material_revision_1.MaterialDefinitionRevision) : {})
196
193
  });
197
194
  if (!found)
198
195
  refuse('REVISION_NOT_FOUND');
@@ -232,7 +229,7 @@ async function createMaterialDefinitionDraft(manager, domainId, creatorId, asked
232
229
  }
233
230
  const previous = await manager.findOne(material_revision_1.MaterialDefinitionRevision, {
234
231
  where: { domain: { id: domainId }, material: { id: material.id } }, order: { revisionNumber: 'DESC' },
235
- ...currentLock(manager)
232
+ ...(0, row_lock_1.writeLockOn)(manager, material_revision_1.MaterialDefinitionRevision)
236
233
  });
237
234
  const revisionNumber = (previous?.revisionNumber ?? 0) + 1;
238
235
  let baseRevisionId = null;
@@ -376,7 +373,7 @@ async function publishRevision(manager, domainId, revisionId, decision) {
376
373
  text(decision.actorId, 'publisher');
377
374
  const previousDecision = await manager.findOne(material_revision_1.MaterialDefinitionPlan, {
378
375
  where: { domain: { id: domainId }, publishedRevision: { id: revisionId } },
379
- ...currentLock(manager)
376
+ ...(0, row_lock_1.writeLockOn)(manager, material_revision_1.MaterialDefinitionPlan)
380
377
  });
381
378
  if (previousDecision)
382
379
  return previousDecision;
@@ -386,12 +383,12 @@ async function publishRevision(manager, domainId, revisionId, decision) {
386
383
  await (0, material_revision_content_1.validateMaterialReferences)(manager, domainId, draft);
387
384
  if (await manager.findOne(material_revision_1.MaterialDefinitionBasis, {
388
385
  where: { domain: { id: domainId }, material: { id: material.id }, businessTime: (0, typeorm_1.MoreThanOrEqual)(draft.effectiveFrom) },
389
- ...currentLock(manager)
386
+ ...(0, row_lock_1.writeLockOn)(manager, material_revision_1.MaterialDefinitionBasis)
390
387
  }))
391
388
  refuse('RETROACTIVE_IMPACT');
392
389
  const oldPlan = await manager.findOne(material_revision_1.MaterialDefinitionPlan, {
393
390
  where: { domain: { id: domainId }, material: { id: material.id }, generation: material.planGeneration },
394
- ...currentLock(manager)
391
+ ...(0, row_lock_1.writeLockOn)(manager, material_revision_1.MaterialDefinitionPlan)
395
392
  });
396
393
  if (material.planGeneration > 0 && !oldPlan)
397
394
  refuse('PLAN_CONFLICT');
@@ -399,7 +396,7 @@ async function publishRevision(manager, domainId, revisionId, decision) {
399
396
  ? await manager.find(material_revision_1.MaterialDefinitionSegment, {
400
397
  where: { domain: { id: domainId }, material: { id: material.id }, plan: { id: oldPlan.id } },
401
398
  order: { validFrom: 'ASC' },
402
- ...currentLock(manager)
399
+ ...(0, row_lock_1.writeLockOn)(manager, material_revision_1.MaterialDefinitionSegment)
403
400
  })
404
401
  : [];
405
402
  // Do not silently erase an already approved future appointment.
@@ -448,7 +445,7 @@ async function resolveMaterialDefinition(manager, domainId, materialId, business
448
445
  const plan = await manager.findOne(material_revision_1.MaterialDefinitionPlan, {
449
446
  where: { domain: { id: domainId }, material: { id: materialId }, recordedAt: (0, typeorm_1.LessThanOrEqual)(known) },
450
447
  order: { generation: 'DESC' },
451
- ...currentLock(manager)
448
+ ...(0, row_lock_1.writeLockOn)(manager, material_revision_1.MaterialDefinitionPlan)
452
449
  });
453
450
  if (!plan)
454
451
  refuse('NO_EFFECTIVE_REVISION');
@@ -458,8 +455,7 @@ async function resolveMaterialDefinition(manager, domainId, materialId, business
458
455
  .where({ domain: { id: domainId }, material: { id: materialId }, plan: { id: plan.id } })
459
456
  .andWhere('segment.validFrom <= :at', { at })
460
457
  .andWhere('(segment.validTo IS NULL OR segment.validTo > :at)', { at });
461
- if (currentLock(manager).lock)
462
- query.setLock('pessimistic_write');
458
+ (0, row_lock_1.lockRowsOf)(manager, query);
463
459
  const segments = await query.getMany();
464
460
  if (!segments.length)
465
461
  await refuseBeforeBaseline(manager, domainId, materialId, at);
@@ -603,7 +599,7 @@ async function captureMaterialDefinitionBasis(manager, domainId, materialId, rec
603
599
  await lockSubject(manager, domainId, materialId);
604
600
  const existing = await manager.findOne(material_revision_1.MaterialDefinitionBasis, {
605
601
  where: { domain: { id: domainId }, recordKey },
606
- ...currentLock(manager)
602
+ ...(0, row_lock_1.writeLockOn)(manager, material_revision_1.MaterialDefinitionBasis)
607
603
  });
608
604
  if (existing) {
609
605
  if (existing.materialId !== materialId || existing.businessTime.getTime() !== at.getTime())