@mudraforge/statutory 2.7.0 → 2.8.0
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/CHANGELOG.md +35 -0
- package/README.md +60 -27
- package/bin/cli.js +359 -16
- package/docs/CORPUS_QUALITY.md +19 -6
- package/docs/PROVENANCE.md +33 -0
- package/okf/manifest.json +1 -1
- package/okf.publication.json +1 -1
- package/package.json +3 -2
- package/src/index.js +1161 -2
- package/types/index.d.ts +104 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,41 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [2.8.0] - 2026-10-25
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Dual-Act Statutory Crosswalk Engine (`crosswalk`)**:
|
|
14
|
+
- Implemented `crosswalkStatute()` in core library and exposed via `npx @mudraforge/statutory crosswalk <section> [--credit <date>] [--payment <date>] [--date <date>] [--json]`.
|
|
15
|
+
- Bridges provisions across the *Income-tax Act, 1961* and *Income-tax Act, 2025* (taking effect 1 April 2026).
|
|
16
|
+
- Enforces statutory withholding crystallization rule: tax withholding liability attaches on the earlier of credit in books or payment disbursement.
|
|
17
|
+
- Cites statutory savings authority under Section 536 of Income-tax Act, 2025 read with Section 6 of General Clauses Act, 1897.
|
|
18
|
+
- Correctly evaluates Section 206C(1H) seller TCS statutory sunset and formal omission w.e.f. 1 April 2025.
|
|
19
|
+
- Supports bidirectional crosswalks (both forward from 1961 Act and backward from 2025 Act).
|
|
20
|
+
- **Semantic Regulatory Diff & Amendment Tracker (`diff`)**:
|
|
21
|
+
- Implemented `getStatutoryDiff()` and exposed via `npx @mudraforge/statutory diff [--section <sec>] [--since <date>] [--domain <domain>] [--json]`.
|
|
22
|
+
- Compiles structured semantic regulatory changelogs across all 5 statutory domains.
|
|
23
|
+
- Tracks key legislative amendments: Section 194H rate cut (5% to 2% w.e.f. 2024-10-01), Section 206C(1H) sunset omission, Rule 48(4) e-invoicing ₹5 Cr threshold, Section 16(2)(aa) GSTR-2B mandatory matching, Section 43B(h) MSME disallowance, and Section 393 2025 Act transition.
|
|
24
|
+
- **StatutoryClient Parity**:
|
|
25
|
+
- Added `client.crosswalk()`, `client.diff()`, `client.verify()`, and `client.doctor()` methods to `StatutoryClient`.
|
|
26
|
+
- **Cryptographic Substrate Verifier (`verify`)**:
|
|
27
|
+
- Implemented `verifyLocalSubstrate()` in core library and exposed via `npx @mudraforge/statutory verify [path]`.
|
|
28
|
+
- Cryptographically verifies local file-level SHA-256 hashes against `manifest.json` (Integrity) and validates root Merkle digests against immutable release roots / Ed25519 signatures (Authenticity).
|
|
29
|
+
- Enforces mandatory statutory baseline policy checks (`194Q`, `194J`, `17(5)`, `MSMED-15-16`, `393`).
|
|
30
|
+
- **Diagnostic Environment Doctor (`doctor`)**:
|
|
31
|
+
- Implemented `runStatutoryDoctor()` in core library and exposed via `npx @mudraforge/statutory doctor [--offline]`.
|
|
32
|
+
- Diagnoses Node.js runtime (`>= v18`), OS platform, environment variables, local OKF filesystem health, and optional Cloudflare Anycast edge round-trip latency.
|
|
33
|
+
- Fully supports air-gapped / isolated VPC environments via `--offline` / `-o` without failing local health guarantees.
|
|
34
|
+
- **Global Machine-Readable Output (`--json`)**:
|
|
35
|
+
- Added `--json` / `-j` flag support across all CLI commands (`verify`, `doctor`, `lookup`, `list`, `browse`, `decode-id`, `check-update`).
|
|
36
|
+
- Emits clean, parseable JSON directly to `stdout` (suppressing banners, spinners, and update prompts) for CI/CD automation and `jq` scripting.
|
|
37
|
+
- **MudraForge Custom Exit Code Contract**:
|
|
38
|
+
- Established standardized exit codes: `0` (Success), `1` (Usage/Syntax error), `2` (Integrity/Security failure), `3` (Statutory policy violation).
|
|
39
|
+
- **Documentation & Release Alignment**:
|
|
40
|
+
- Unified package, manifest, and publication versions at `v2.8.0`.
|
|
41
|
+
- Extracted formal audit ledger into `docs/PROVENANCE.md` and streamlined main `README.md` for fast developer onboarding.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
10
45
|
## [2.7.0] - 2026-10-25
|
|
11
46
|
|
|
12
47
|
### Added
|
package/README.md
CHANGED
|
@@ -36,37 +36,21 @@ console.log(statute.statute.title); // MSMED statutory payment timelines & compo
|
|
|
36
36
|
|
|
37
37
|
---
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
## Overview
|
|
40
40
|
|
|
41
|
-
`@mudraforge/statutory`
|
|
41
|
+
`@mudraforge/statutory` is an offline, zero-dependency statutory knowledge substrate for Indian and cross-border financial compliance (Direct Tax, GST, MSME, Corporate M&A, International Tax). It grounds withholding rates, cumulative thresholds, and filing timelines in primary legislation and circulars rather than ungrounded LLM text generation.
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
* **Deterministic Knowledge**: Provides structured, offline rule definitions (`okf/**/section.json`) and ISO Mod-36 identifier decoding without outbound network calls.
|
|
44
|
+
* **Dual-Act Temporal Boundary**: Explicitly models both the *Income-tax Act, 1961* and the *Income-tax Act, 2025* (effective 1 April 2026), evaluating date-bound transitional savings rules automatically.
|
|
45
|
+
* **Separation of Concerns**: This open-source commons provides declarative statutory truth; downstream transactional execution, payment authorization, and bank disbursements belong to the governed execution layer (Adesha).
|
|
44
46
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
## Published Release & Provenance Ledger
|
|
48
|
-
|
|
49
|
-
| Item | Value |
|
|
50
|
-
|---|---|
|
|
51
|
-
| **Package Release** | **`v2.4.2`** (Public Beta) |
|
|
52
|
-
| **Knowledge Specification** | **Google Open Knowledge Format (OKF v0.2) + MudraForge Statutory Schema Extension** |
|
|
53
|
-
| **Regulatory Snapshot** | **CBDT / CBIC / MCA / MSME Baseline: October 2026** |
|
|
54
|
-
| **Corpus Quality Card** | 100% Citation Resolution (15/15 URLs) in [`docs/CORPUS_QUALITY.md`](./docs/CORPUS_QUALITY.md) |
|
|
55
|
-
| **Architecture Decision Records** | 6 Normative ADRs documented in [`docs/ARCHITECTURE_DECISIONS.md`](./docs/ARCHITECTURE_DECISIONS.md) |
|
|
56
|
-
| **Vendor Conformance Suite** | 12/12 Passing Fixture Tests in [`conformance/`](./conformance/) |
|
|
57
|
-
| **End-to-End Execution Trace** | Audited 7-stage AP lifecycle in [`examples/`](./examples/) (`npm run example:trace`) |
|
|
58
|
-
| **Enterprise Assurance Preview** | Formal TLA+ & mutation defenses in [`docs/ADESHA_ASSURANCE_OVERVIEW.md`](./docs/ADESHA_ASSURANCE_OVERVIEW.md) |
|
|
59
|
-
| **Integrity Root** | SHA-256 root manifest in [`okf/manifest.json`](./okf/manifest.json) (reproducible byte-level non-tampering proof) |
|
|
60
|
-
| **Temporal Framework** | Dual-Act Bridge: *Income-tax Act, 1961* & *Income-tax Act, 2025* |
|
|
61
|
-
| **Verification Gate** | 100% Pass across cumulative thresholds, legislative sunsets, and excess math |
|
|
62
|
-
|
|
63
|
-
> **Note on Cryptographic Scope**: The SHA-256 root hash in `okf/manifest.json` provides mathematical verification of **byte-level integrity** (ensuring local files have not been corrupted, altered, or partially written). A hash certifies that bytes match the published release; it does not by itself constitute legal or statutory authority. Epistemic authority is established strictly via the primary gazette citations, CBDT/CBIC executive circulars, and independent ground-truth tests.
|
|
47
|
+
> For cryptographic root digests, normative specifications, and formal assurance artifacts, see [docs/PROVENANCE.md](./docs/PROVENANCE.md).
|
|
64
48
|
|
|
65
49
|
---
|
|
66
50
|
|
|
67
51
|
## Table of Contents
|
|
68
52
|
|
|
69
|
-
- [
|
|
53
|
+
- [Overview](#overview)
|
|
70
54
|
- [Quick Start (Free Tier)](#quick-start-free-tier)
|
|
71
55
|
- [1. Installation via npm](#1-installation-via-npm)
|
|
72
56
|
- [2. Initialize AI Agent Skill](#2-initialize-ai-agent-skill)
|
|
@@ -81,7 +65,7 @@ console.log(statute.statute.title); // MSMED statutory payment timelines & compo
|
|
|
81
65
|
- [Structured OKF Directory Hierarchy](#structured-okf-domain-hierarchy-zero-token-discovery)
|
|
82
66
|
- [Canonical Edge Synchronization](#canonical-edge-synchronization-full-statutory-universe)
|
|
83
67
|
- [Enterprise Tier (Paid Cloud Execution)](#enterprise-tier-paid-cloud-execution--analysis-mode)
|
|
84
|
-
- [Edge API Endpoints &
|
|
68
|
+
- [Edge API Endpoints & Rate Limits](#enterprise-edge-api-endpoints--rate-limits)
|
|
85
69
|
- [Error Semantics & Response Codes](#error-semantics--response-codes)
|
|
86
70
|
- [System Origin & Validation Baseline](#system-origin--validation-baseline)
|
|
87
71
|
- [The Payment Instruction Boundary & End-to-End AP Architecture](#the-payment-instruction-boundary--end-to-end-ap-architecture)
|
|
@@ -427,6 +411,55 @@ const isActive2024 = await isStatuteActive('206C(1H)', '2024-05-01'); // true
|
|
|
427
411
|
const isActive2025 = await isStatuteActive('206C(1H)', '2025-06-01'); // false (omitted by Finance Act, 2025)
|
|
428
412
|
```
|
|
429
413
|
|
|
414
|
+
### 4. CLI Verification, Auditing & Pipeline Integration
|
|
415
|
+
|
|
416
|
+
The CLI provides cryptographic provenance checking, offline integrity verification, and machine-readable JSON output for CI/CD audit pipelines:
|
|
417
|
+
|
|
418
|
+
```bash
|
|
419
|
+
# Run full environment, local substrate health, and edge latency diagnostics
|
|
420
|
+
npx @mudraforge/statutory doctor
|
|
421
|
+
|
|
422
|
+
# Force air-gapped / offline diagnostics (zero outbound network requests)
|
|
423
|
+
npx @mudraforge/statutory doctor --offline
|
|
424
|
+
|
|
425
|
+
# Cryptographically verify local statutory substrate integrity & origin authenticity
|
|
426
|
+
npx @mudraforge/statutory verify
|
|
427
|
+
|
|
428
|
+
# Dual-act 1961 <-> 2025 statutory crosswalk (earlier of credit vs payment)
|
|
429
|
+
npx @mudraforge/statutory crosswalk 194Q --credit 2026-03-25 --payment 2026-04-10
|
|
430
|
+
|
|
431
|
+
# Reverse crosswalk from 2025 Act provision
|
|
432
|
+
npx @mudraforge/statutory crosswalk 393 --date 2025-08-01
|
|
433
|
+
|
|
434
|
+
# Query semantic regulatory changelog and amendment tracker
|
|
435
|
+
npx @mudraforge/statutory diff --section 194H
|
|
436
|
+
npx @mudraforge/statutory diff --since 2025-01-01
|
|
437
|
+
npx @mudraforge/statutory diff --domain indirect_tax_gst
|
|
438
|
+
|
|
439
|
+
# Output machine-parseable JSON for CI/CD or jq integration
|
|
440
|
+
npx @mudraforge/statutory crosswalk 194Q --credit 2026-03-25 --payment 2026-04-10 --json
|
|
441
|
+
npx @mudraforge/statutory diff --section 194H --json
|
|
442
|
+
npx @mudraforge/statutory verify --json
|
|
443
|
+
npx @mudraforge/statutory doctor --json --offline
|
|
444
|
+
|
|
445
|
+
# Query section in pure JSON format
|
|
446
|
+
npx @mudraforge/statutory lookup 194Q --json
|
|
447
|
+
|
|
448
|
+
# Decode and validate PAN / GSTIN offline with ISO Mod-36 checksum
|
|
449
|
+
npx @mudraforge/statutory decode-id 27AAPFU0939F1ZV --json
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
#### Custom MudraForge Exit Code Contract
|
|
453
|
+
Calling pipelines and CI/CD audit steps can inspect `$?` directly:
|
|
454
|
+
|
|
455
|
+
| Exit Code | Classification | Meaning |
|
|
456
|
+
|---|---|---|
|
|
457
|
+
| `0` | `SUCCESS` | Substrate verified authentic, intact, and all baseline provisions present. |
|
|
458
|
+
| `1` | `USAGE_OR_OPERATIONAL_ERROR` | Invalid CLI arguments, unknown command, or syntax error. |
|
|
459
|
+
| `2` | `INTEGRITY_OR_SECURITY_VIOLATION` | Cryptographic signature failure, unpinned root hash, file tampering, or revoked key. |
|
|
460
|
+
| `3` | `STATUTORY_POLICY_VIOLATION` | Statutory policy failure (e.g. missing mandatory baseline provision or sunset violation). |
|
|
461
|
+
|
|
462
|
+
|
|
430
463
|
---
|
|
431
464
|
|
|
432
465
|
## Enterprise Tier (Paid Cloud Execution & Analysis Mode)
|
|
@@ -453,9 +486,9 @@ console.log(result.taxableBase); // ₹70,00,000 (Calculates strictly on excess
|
|
|
453
486
|
console.log(result.taxAmount); // ₹7,000 (0.1% TDS on taxable base)
|
|
454
487
|
```
|
|
455
488
|
|
|
456
|
-
### Enterprise Edge API Endpoints &
|
|
489
|
+
### Enterprise Edge API Endpoints & Rate Limits
|
|
457
490
|
|
|
458
|
-
| Endpoint | Method | Auth Required |
|
|
491
|
+
| Endpoint | Method | Auth Required | Rate Limit (Per Client / IP) | Response |
|
|
459
492
|
|---|---|---|---|---|
|
|
460
493
|
| `/api/v1/statutory/sections` | `GET` | No | 180 req/min | JSON array of indexed OKF sections |
|
|
461
494
|
| `/api/v1/statutory/lookup` | `GET` | No | 180 req/min | Parametric section details & temporal validity |
|
|
@@ -487,7 +520,7 @@ All error responses from `@mudraforge/statutory` and the hosted edge API return
|
|
|
487
520
|
| `405 Method Not Allowed` | `METHOD_NOT_ALLOWED` | Non-GET request sent to read-only commons endpoints. |
|
|
488
521
|
| `413 Payload Too Large` | `PAYLOAD_TOO_LARGE` | JSON request body on `/calculate` exceeds 64KB security threshold. |
|
|
489
522
|
| `415 Unsupported Media` | `UNSUPPORTED_MEDIA_TYPE` | Non-JSON payload sent to calculation engine. Set `Content-Type: application/json`. |
|
|
490
|
-
| `429 Too Many Requests` | `RATE_LIMIT_EXCEEDED` |
|
|
523
|
+
| `429 Too Many Requests` | `RATE_LIMIT_EXCEEDED` | Rate limit threshold breached. Check `Retry-After` header before retrying. |
|
|
491
524
|
|
|
492
525
|
---
|
|
493
526
|
|
package/bin/cli.js
CHANGED
|
@@ -18,6 +18,11 @@ import {
|
|
|
18
18
|
getLocalManifest,
|
|
19
19
|
checkForPackageUpdate,
|
|
20
20
|
decodeTaxIdentifier,
|
|
21
|
+
verifyLocalSubstrate,
|
|
22
|
+
runStatutoryDoctor,
|
|
23
|
+
crosswalkStatute,
|
|
24
|
+
getStatutoryDiff,
|
|
25
|
+
STATUTORY_EXIT_CODES,
|
|
21
26
|
STATUTORY_CLIENT_VERSION,
|
|
22
27
|
DEFAULT_LOCAL_OKF_DIR
|
|
23
28
|
} from '../src/index.js';
|
|
@@ -25,11 +30,65 @@ import {
|
|
|
25
30
|
const __filename = fileURLToPath(import.meta.url);
|
|
26
31
|
const __dirname = path.dirname(__filename);
|
|
27
32
|
|
|
28
|
-
const
|
|
33
|
+
const rawArgs = process.argv.slice(2);
|
|
34
|
+
const isJsonMode = rawArgs.includes('--json') || rawArgs.includes('-j');
|
|
35
|
+
const isOfflineMode = rawArgs.includes('--offline') || rawArgs.includes('-o');
|
|
36
|
+
const args = rawArgs.filter(a => a !== '--json' && a !== '-j' && a !== '--offline' && a !== '-o');
|
|
29
37
|
const command = args[0] ? args[0].toLowerCase() : 'help';
|
|
30
38
|
|
|
39
|
+
function getNamedArg(nameList, argList) {
|
|
40
|
+
for (let i = 0; i < argList.length; i++) {
|
|
41
|
+
const item = argList[i];
|
|
42
|
+
for (const name of nameList) {
|
|
43
|
+
if (item === name && i + 1 < argList.length && !argList[i + 1].startsWith('-')) {
|
|
44
|
+
return argList[i + 1];
|
|
45
|
+
}
|
|
46
|
+
if (item.startsWith(`${name}=`)) {
|
|
47
|
+
return item.slice(name.length + 1);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
31
54
|
async function main() {
|
|
32
55
|
switch (command) {
|
|
56
|
+
case 'doctor':
|
|
57
|
+
case 'diagnose':
|
|
58
|
+
await handleDoctor();
|
|
59
|
+
break;
|
|
60
|
+
|
|
61
|
+
case 'verify':
|
|
62
|
+
case 'audit':
|
|
63
|
+
case 'check':
|
|
64
|
+
await handleVerify(args[1]);
|
|
65
|
+
break;
|
|
66
|
+
|
|
67
|
+
case 'crosswalk':
|
|
68
|
+
case 'bridge':
|
|
69
|
+
case 'transition': {
|
|
70
|
+
const section = (args[1] && !args[1].startsWith('-')) ? args[1] : getNamedArg(['--section', '-s'], rawArgs);
|
|
71
|
+
const creditDate = getNamedArg(['--credit', '-c'], rawArgs);
|
|
72
|
+
const paymentDate = getNamedArg(['--payment', '-p'], rawArgs);
|
|
73
|
+
const asOfDate = getNamedArg(['--date', '-d', '--as-of'], rawArgs);
|
|
74
|
+
if (!section) {
|
|
75
|
+
console.error('[ERROR] Please specify a section to crosswalk, e.g. npx @mudraforge/statutory crosswalk 194Q --credit 2026-03-25 --payment 2026-04-10');
|
|
76
|
+
process.exit(STATUTORY_EXIT_CODES.USAGE_ERROR);
|
|
77
|
+
}
|
|
78
|
+
await handleCrosswalk(section, { creditDate, paymentDate, asOfDate });
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
case 'diff':
|
|
83
|
+
case 'changelog':
|
|
84
|
+
case 'amendments': {
|
|
85
|
+
const section = (args[1] && !args[1].startsWith('-')) ? args[1] : getNamedArg(['--section', '-s'], rawArgs);
|
|
86
|
+
const since = getNamedArg(['--since'], rawArgs);
|
|
87
|
+
const domain = getNamedArg(['--domain'], rawArgs);
|
|
88
|
+
await handleDiff({ section, since, domain });
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
|
|
33
92
|
case 'init-skill':
|
|
34
93
|
case 'install-skill':
|
|
35
94
|
await handleInitSkill(args[1]);
|
|
@@ -90,7 +149,7 @@ async function main() {
|
|
|
90
149
|
}
|
|
91
150
|
|
|
92
151
|
// Non-intrusive background npm package update notice
|
|
93
|
-
if (!['check-update', 'status', 'version'].includes(command)) {
|
|
152
|
+
if (!isJsonMode && !['check-update', 'status', 'version', 'verify', 'audit', 'check', 'crosswalk', 'bridge', 'transition', 'diff', 'changelog', 'amendments'].includes(command)) {
|
|
94
153
|
try {
|
|
95
154
|
const updateInfo = await checkForPackageUpdate();
|
|
96
155
|
if (updateInfo && updateInfo.updateAvailable) {
|
|
@@ -103,23 +162,244 @@ async function main() {
|
|
|
103
162
|
}
|
|
104
163
|
}
|
|
105
164
|
|
|
165
|
+
async function handleVerify(targetDir) {
|
|
166
|
+
try {
|
|
167
|
+
let dest = DEFAULT_LOCAL_OKF_DIR;
|
|
168
|
+
if (targetDir) {
|
|
169
|
+
dest = path.resolve(process.cwd(), targetDir);
|
|
170
|
+
}
|
|
171
|
+
const report = verifyLocalSubstrate(dest);
|
|
172
|
+
if (isJsonMode) {
|
|
173
|
+
console.log(JSON.stringify(report, null, 2));
|
|
174
|
+
process.exit(report.exitCode);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
console.log('\n===========================================================================');
|
|
178
|
+
console.log(`MUDRAFORGE OPEN KNOWLEDGE FORMAT (OKF) — CRYPTOGRAPHIC VERIFIER [v${report.okfVersion || 'Unknown'}]`);
|
|
179
|
+
console.log('===========================================================================');
|
|
180
|
+
|
|
181
|
+
if (report.integrity.valid) {
|
|
182
|
+
console.log(`[OK] Integrity: ${report.integrity.totalFilesChecked} files verified against Merkle manifest.`);
|
|
183
|
+
console.log(` Root Merkle Hash: ${report.integrity.computedRoot}`);
|
|
184
|
+
} else {
|
|
185
|
+
console.error(`[FAIL] Integrity: Mismatches: ${report.integrity.mismatches.length}, Missing: ${report.integrity.missingFiles.length}, Untracked: ${report.integrity.untrackedFiles.length}`);
|
|
186
|
+
for (const m of report.integrity.mismatches) {
|
|
187
|
+
console.error(` • ${m.file} (Expected ${m.expected.slice(0, 10)}..., actual ${m.actual.slice(0, 10)}...)`);
|
|
188
|
+
}
|
|
189
|
+
for (const mf of report.integrity.missingFiles) {
|
|
190
|
+
console.error(` • Missing: ${mf}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (report.authenticity.valid) {
|
|
195
|
+
console.log(`[OK] Authenticity: Provenance authenticated via ${report.authenticity.method}.`);
|
|
196
|
+
if (report.authenticity.keyId) {
|
|
197
|
+
console.log(` Signer Key ID: ${report.authenticity.keyId}`);
|
|
198
|
+
}
|
|
199
|
+
} else {
|
|
200
|
+
console.error(`[FAIL] Authenticity: Root hash "${report.integrity.computedRoot}" is neither pinned nor signed by an authorized MudraForge trust anchor.`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (report.baselineCheck.valid) {
|
|
204
|
+
console.log(`[OK] Statutory Policy: All mandatory baseline provisions present (${report.baselineCheck.baselineSections.join(', ')}).`);
|
|
205
|
+
} else {
|
|
206
|
+
console.error(`[FAIL] Statutory Policy: Missing mandatory baseline provisions: ${report.baselineCheck.missingBaseline.join(', ')}`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
console.log('───────────────────────────────────────────────────────────────────────────');
|
|
210
|
+
if (report.valid) {
|
|
211
|
+
console.log(`[PASS] Statutory substrate is authentic, intact, and compliant (Exit ${report.exitCode}).\n`);
|
|
212
|
+
} else {
|
|
213
|
+
console.error(`[FAIL] Verification failed with Exit Code ${report.exitCode}.\n`);
|
|
214
|
+
}
|
|
215
|
+
process.exit(report.exitCode);
|
|
216
|
+
} catch (err) {
|
|
217
|
+
if (isJsonMode) {
|
|
218
|
+
console.log(JSON.stringify({ valid: false, exitCode: STATUTORY_EXIT_CODES.USAGE_ERROR, error: err.message }, null, 2));
|
|
219
|
+
} else {
|
|
220
|
+
console.error(`[ERROR] Verification error: ${err.message}`);
|
|
221
|
+
}
|
|
222
|
+
process.exit(STATUTORY_EXIT_CODES.USAGE_ERROR);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function handleDoctor() {
|
|
227
|
+
try {
|
|
228
|
+
const report = await runStatutoryDoctor({ offline: isOfflineMode });
|
|
229
|
+
if (isJsonMode) {
|
|
230
|
+
console.log(JSON.stringify(report, null, 2));
|
|
231
|
+
process.exit(report.exitCode);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
console.log('\n===========================================================================');
|
|
235
|
+
console.log(`MUDRAFORGE STATUTORY DIAGNOSTIC DOCTOR [v${report.clientVersion}]`);
|
|
236
|
+
console.log('===========================================================================');
|
|
237
|
+
|
|
238
|
+
console.log('── 1. RUNTIME & HOST ENVIRONMENT ──');
|
|
239
|
+
console.log(`Node.js: ${report.runtime.nodeVersion} ${report.runtime.isSupportedNode ? '(Supported: v18+)' : '[UNSUPPORTED]'}`);
|
|
240
|
+
console.log(`Platform: ${report.runtime.platform} (${report.runtime.arch})`);
|
|
241
|
+
console.log(`Environment: ${report.runtime.environment}${report.runtime.isCI ? ' (CI Active)' : ''}`);
|
|
242
|
+
console.log(`API Key Config: ${report.runtime.hasApiKey ? 'CONFIGURED (Enterprise Tier Enabled)' : 'NOT_CONFIGURED (Free Commons Mode)'}`);
|
|
243
|
+
|
|
244
|
+
console.log('\n── 2. LOCAL OKF SUBSTRATE HEALTH ──');
|
|
245
|
+
if (report.substrate.valid) {
|
|
246
|
+
console.log(`Directory: ${DEFAULT_LOCAL_OKF_DIR}`);
|
|
247
|
+
console.log(`Manifest Version: v${report.substrate.okfVersion} (Published: ${report.substrate.publishedAt})`);
|
|
248
|
+
console.log(`Files Checked: ${report.substrate.integrity.totalFilesChecked} (0 mismatches, 0 missing, 0 untracked)`);
|
|
249
|
+
console.log(`Root Merkle Hash: ${report.substrate.integrity.computedRoot}`);
|
|
250
|
+
console.log(`Integrity: PASS`);
|
|
251
|
+
console.log(`Provenance: AUTHENTIC (${report.substrate.authenticity.method})`);
|
|
252
|
+
console.log(`Statutory Policy: PASS (All ${report.substrate.baselineCheck.baselineSections.length} mandatory baselines present)`);
|
|
253
|
+
} else {
|
|
254
|
+
console.error(`Substrate: FAILED (Exit Code ${report.substrate.exitCode})`);
|
|
255
|
+
if (report.substrate.error) console.error(`Error: ${report.substrate.error}`);
|
|
256
|
+
if (!report.substrate.integrity.valid) {
|
|
257
|
+
console.error(`Integrity Failure: ${report.substrate.integrity.mismatches.length} mismatch(es), ${report.substrate.integrity.missingFiles.length} missing files.`);
|
|
258
|
+
}
|
|
259
|
+
if (!report.substrate.authenticity.valid) {
|
|
260
|
+
console.error(`Authenticity: Root hash not authenticated.`);
|
|
261
|
+
}
|
|
262
|
+
if (!report.substrate.baselineCheck.valid) {
|
|
263
|
+
console.error(`Missing Baselines: ${report.substrate.baselineCheck.missingBaseline.join(', ')}`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
console.log('\n── 3. CLOUDFLARE ANYCAST EDGE CONNECTIVITY ──');
|
|
268
|
+
console.log(`Endpoint: ${report.edge.baseUrl}`);
|
|
269
|
+
if (report.edge.status === 'REACHABLE') {
|
|
270
|
+
console.log(`Status: REACHABLE (Latency: ${report.edge.latencyMs}ms)`);
|
|
271
|
+
} else if (report.edge.status === 'SKIPPED_AIR_GAPPED') {
|
|
272
|
+
console.log(`Status: SKIPPED (Air-Gapped / Offline Flag Enabled)`);
|
|
273
|
+
} else {
|
|
274
|
+
console.log(`Status: ${report.edge.status} (Offline-first local mode operative)`);
|
|
275
|
+
}
|
|
276
|
+
console.log(`Advisory: ${report.edge.advisory}`);
|
|
277
|
+
|
|
278
|
+
console.log('───────────────────────────────────────────────────────────────────────────');
|
|
279
|
+
if (report.healthy) {
|
|
280
|
+
console.log(`[PASS] All diagnostic checks passed with zero defects (Exit 0).\n`);
|
|
281
|
+
} else {
|
|
282
|
+
console.error(`[FAIL] Diagnostic doctor flagged health violations (Exit ${report.exitCode}).\n`);
|
|
283
|
+
}
|
|
284
|
+
process.exit(report.exitCode);
|
|
285
|
+
} catch (err) {
|
|
286
|
+
if (isJsonMode) {
|
|
287
|
+
console.log(JSON.stringify({ healthy: false, exitCode: STATUTORY_EXIT_CODES.USAGE_ERROR, error: err.message }, null, 2));
|
|
288
|
+
} else {
|
|
289
|
+
console.error(`[ERROR] Doctor diagnostic error: ${err.message}`);
|
|
290
|
+
}
|
|
291
|
+
process.exit(STATUTORY_EXIT_CODES.USAGE_ERROR);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async function handleCrosswalk(section, options) {
|
|
296
|
+
try {
|
|
297
|
+
const result = crosswalkStatute(section, options);
|
|
298
|
+
if (isJsonMode) {
|
|
299
|
+
console.log(JSON.stringify(result, null, 2));
|
|
300
|
+
process.exit(0);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
console.log('\n===========================================================================');
|
|
304
|
+
console.log('MUDRAFORGE DUAL-ACT STATUTORY CROSSWALK (1961 ACT <-> 2025 ACT)');
|
|
305
|
+
console.log('===========================================================================');
|
|
306
|
+
console.log(`Query Section: ${result.querySection}`);
|
|
307
|
+
console.log(`Governing Statute: ${result.governingAct}`);
|
|
308
|
+
console.log(`Applicable Section: ${result.applicableSection}`);
|
|
309
|
+
console.log(`Temporal Status: ${result.temporalStatus}`);
|
|
310
|
+
|
|
311
|
+
console.log('\n── TEMPORAL CRYSTALLIZATION (EARLIER OF CREDIT OR PAYMENT) ──');
|
|
312
|
+
if (result.creditDate) console.log(`Credit in Books: ${result.creditDate}`);
|
|
313
|
+
if (result.paymentDate) console.log(`Payment Rail: ${result.paymentDate}`);
|
|
314
|
+
console.log(`Trigger Date: ${result.triggerDate}`);
|
|
315
|
+
console.log(`Trigger Event: ${result.triggerReason}`);
|
|
316
|
+
console.log(`Event Context: ${result.triggerDescription}`);
|
|
317
|
+
|
|
318
|
+
console.log('\n── STATUTORY BRIDGE & DUAL-ACT MAPPING ──');
|
|
319
|
+
console.log(`Predecessor (1961): ${result.crosswalk.predecessor.statutorySection} (${result.crosswalk.predecessor.title})`);
|
|
320
|
+
console.log(` Rate: ${result.crosswalk.predecessor.standardRate} | Challan: ${result.crosswalk.predecessor.challan} | Return: ${result.crosswalk.predecessor.returnForm}`);
|
|
321
|
+
console.log(`Successor (2025): ${result.crosswalk.successor.statutorySection} (Effective ${result.crosswalk.successor.effectiveDate})`);
|
|
322
|
+
console.log(` Rate: ${result.crosswalk.successor.standardRate} | Return: ${result.crosswalk.successor.returnForm}`);
|
|
323
|
+
|
|
324
|
+
console.log('\n── STATUTORY SAVINGS & LEGAL GROUNDING ──');
|
|
325
|
+
console.log(`Primary Authority: ${result.crosswalk.savingsAuthority.statute}`);
|
|
326
|
+
console.log(`Subsidiary Principle: ${result.crosswalk.savingsAuthority.subsidiary}`);
|
|
327
|
+
console.log(`Legal Analysis: ${result.legalAnalysis}`);
|
|
328
|
+
|
|
329
|
+
console.log('───────────────────────────────────────────────────────────────────────────');
|
|
330
|
+
console.log('[PASS] Statutory crosswalk resolved successfully (Exit 0).\n');
|
|
331
|
+
process.exit(0);
|
|
332
|
+
} catch (err) {
|
|
333
|
+
if (isJsonMode) {
|
|
334
|
+
console.log(JSON.stringify({ status: 'ERROR', exitCode: STATUTORY_EXIT_CODES.USAGE_ERROR, error: err.message }, null, 2));
|
|
335
|
+
} else {
|
|
336
|
+
console.error(`[ERROR] Crosswalk failure: ${err.message}`);
|
|
337
|
+
}
|
|
338
|
+
process.exit(STATUTORY_EXIT_CODES.USAGE_ERROR);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
async function handleDiff(options) {
|
|
343
|
+
try {
|
|
344
|
+
const result = getStatutoryDiff(options);
|
|
345
|
+
if (isJsonMode) {
|
|
346
|
+
console.log(JSON.stringify(result, null, 2));
|
|
347
|
+
process.exit(0);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
console.log('\n===========================================================================');
|
|
351
|
+
console.log('MUDRAFORGE STATUTORY REGULATORY CHANGELOG & AMENDMENT TRACKER');
|
|
352
|
+
console.log('===========================================================================');
|
|
353
|
+
console.log(`Filter Section: ${result.filterSection || 'ALL SECTIONS'}`);
|
|
354
|
+
console.log(`Filter Since: ${result.filterSince || 'ALL TIME'}`);
|
|
355
|
+
console.log(`Filter Domain: ${result.filterDomain || 'ALL DOMAINS'}`);
|
|
356
|
+
console.log(`Total Amendments: ${result.totalAmendments}`);
|
|
357
|
+
|
|
358
|
+
if (result.amendments.length === 0) {
|
|
359
|
+
console.log('\n[NOTICE] No statutory amendments matched the specified query filters.');
|
|
360
|
+
} else {
|
|
361
|
+
for (const am of result.amendments) {
|
|
362
|
+
console.log(`\n── [${am.id}] ${am.sectionCode} (${am.domain}) ──`);
|
|
363
|
+
console.log(`Title: ${am.title}`);
|
|
364
|
+
console.log(`Effective Date: ${am.effectiveDate} (${am.amendmentType})`);
|
|
365
|
+
console.log(`Governing Statute: ${am.statute}`);
|
|
366
|
+
console.log(`Enacted By: ${am.legalAuthority}`);
|
|
367
|
+
console.log(`Previous Baseline: ${am.previousValue}`);
|
|
368
|
+
console.log(`Amended Baseline: ${am.amendedValue}`);
|
|
369
|
+
console.log(`Summary: ${am.changeSummary}`);
|
|
370
|
+
console.log(`Statutory Impact: ${am.statutoryImpact}`);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
console.log('───────────────────────────────────────────────────────────────────────────');
|
|
375
|
+
console.log(`[PASS] Statutory diff completed successfully (${result.totalAmendments} amendment(s) found) (Exit 0).\n`);
|
|
376
|
+
process.exit(0);
|
|
377
|
+
} catch (err) {
|
|
378
|
+
if (isJsonMode) {
|
|
379
|
+
console.log(JSON.stringify({ status: 'ERROR', exitCode: STATUTORY_EXIT_CODES.USAGE_ERROR, error: err.message }, null, 2));
|
|
380
|
+
} else {
|
|
381
|
+
console.error(`[ERROR] Statutory diff failure: ${err.message}`);
|
|
382
|
+
}
|
|
383
|
+
process.exit(STATUTORY_EXIT_CODES.USAGE_ERROR);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
106
387
|
async function handleCheckUpdate() {
|
|
107
388
|
try {
|
|
108
|
-
|
|
389
|
+
let pkgUpdate = null;
|
|
109
390
|
try {
|
|
110
|
-
|
|
111
|
-
if (pkgUpdate && pkgUpdate.updateAvailable) {
|
|
112
|
-
console.log('───────────────────────────────────────────────────────────────────────────');
|
|
113
|
-
console.log(`[WARN] New npm package version available: v${pkgUpdate.latestVersion} (installed: v${pkgUpdate.currentVersion})`);
|
|
114
|
-
console.log(`Run: npm install @mudraforge/statutory@latest`);
|
|
115
|
-
console.log('───────────────────────────────────────────────────────────────────────────');
|
|
116
|
-
} else if (pkgUpdate) {
|
|
117
|
-
console.log(`[OK] npm package code is up to date (v${pkgUpdate.currentVersion}).`);
|
|
118
|
-
}
|
|
391
|
+
pkgUpdate = await checkForPackageUpdate({ cacheTtlMs: 0 });
|
|
119
392
|
} catch (_) {}
|
|
120
393
|
|
|
121
|
-
console.log('\n[INFO] Checking OKF knowledge substrate status against Cloudflare edge...');
|
|
122
394
|
const status = await checkUpdates();
|
|
395
|
+
|
|
396
|
+
if (isJsonMode) {
|
|
397
|
+
console.log(JSON.stringify({
|
|
398
|
+
packageUpdate: pkgUpdate,
|
|
399
|
+
substrateStatus: status
|
|
400
|
+
}, null, 2));
|
|
401
|
+
process.exit(0);
|
|
402
|
+
}
|
|
123
403
|
console.log('───────────────────────────────────────────────────────────────────────────');
|
|
124
404
|
console.log(`Local OKF Version: ${status.localVersion}`);
|
|
125
405
|
console.log(`Latest Edge Version: ${status.latestVersion}`);
|
|
@@ -186,6 +466,18 @@ async function handleSync(targetDir) {
|
|
|
186
466
|
async function handleBrowse() {
|
|
187
467
|
const manifest = getLocalManifest();
|
|
188
468
|
|
|
469
|
+
if (isJsonMode) {
|
|
470
|
+
console.log(JSON.stringify(manifest ? {
|
|
471
|
+
okfVersion: manifest.okfVersion,
|
|
472
|
+
publishedAt: manifest.publishedAt,
|
|
473
|
+
rootHash: manifest.rootHash,
|
|
474
|
+
totalSections: manifest.totalSections,
|
|
475
|
+
domains: manifest.domains,
|
|
476
|
+
sections: manifest.sections
|
|
477
|
+
} : { okfVersion: null, sections: [] }, null, 2));
|
|
478
|
+
process.exit(0);
|
|
479
|
+
}
|
|
480
|
+
|
|
189
481
|
console.log('\n===========================================================================');
|
|
190
482
|
console.log(`OPEN KNOWLEDGE FORMAT (OKF) — LOCAL BROWSER [v${manifest ? manifest.okfVersion : 'Local'}]`);
|
|
191
483
|
console.log('===========================================================================');
|
|
@@ -283,18 +575,28 @@ async function handleLookup(section, asOfDate) {
|
|
|
283
575
|
let temporalStatus = 'ACTIVE';
|
|
284
576
|
|
|
285
577
|
if (s) {
|
|
286
|
-
console.log(`\n[LOCAL OKF CACHE] Loaded section [${cleanSection}] from disk.`);
|
|
578
|
+
if (!isJsonMode) console.log(`\n[LOCAL OKF CACHE] Loaded section [${cleanSection}] from disk.`);
|
|
287
579
|
if (asOfDate) {
|
|
288
580
|
if (asOfDate < s.effectivePeriod.validFrom) temporalStatus = 'PRE_EFFECTIVE';
|
|
289
581
|
else if (s.effectivePeriod.validTo && asOfDate > s.effectivePeriod.validTo) temporalStatus = 'SUNSET_SUPERSEDED';
|
|
290
582
|
}
|
|
291
583
|
} else {
|
|
292
|
-
console.log(`\n[EDGE LOOKUP] Querying MudraForge Live Commons for [${cleanSection}]${asOfDate ? ` (as-of: ${asOfDate})` : ''}...`);
|
|
584
|
+
if (!isJsonMode) console.log(`\n[EDGE LOOKUP] Querying MudraForge Live Commons for [${cleanSection}]${asOfDate ? ` (as-of: ${asOfDate})` : ''}...`);
|
|
293
585
|
const remote = await lookupStatute(cleanSection, { asOfDate });
|
|
294
586
|
s = remote.statute;
|
|
295
587
|
temporalStatus = remote.temporalStatus;
|
|
296
588
|
}
|
|
297
589
|
|
|
590
|
+
if (isJsonMode) {
|
|
591
|
+
console.log(JSON.stringify({
|
|
592
|
+
status: 'SUCCESS',
|
|
593
|
+
sectionCode: cleanSection,
|
|
594
|
+
temporalStatus,
|
|
595
|
+
statute: s
|
|
596
|
+
}, null, 2));
|
|
597
|
+
process.exit(0);
|
|
598
|
+
}
|
|
599
|
+
|
|
298
600
|
console.log('───────────────────────────────────────────────────────────────────────────');
|
|
299
601
|
console.log(`Section: ${s.statutorySection} (${s.governingStatute})`);
|
|
300
602
|
console.log(`Title: ${s.title}`);
|
|
@@ -324,6 +626,14 @@ async function handleLookup(section, asOfDate) {
|
|
|
324
626
|
}
|
|
325
627
|
console.log('───────────────────────────────────────────────────────────────────────────\n');
|
|
326
628
|
} catch (err) {
|
|
629
|
+
if (isJsonMode) {
|
|
630
|
+
console.error(JSON.stringify({
|
|
631
|
+
status: 'ERROR',
|
|
632
|
+
error: err.message,
|
|
633
|
+
availableSections: err.availableSections || []
|
|
634
|
+
}, null, 2));
|
|
635
|
+
process.exit(1);
|
|
636
|
+
}
|
|
327
637
|
console.error(`[ERROR] Lookup failed: ${err.message}`);
|
|
328
638
|
if (err.availableSections) {
|
|
329
639
|
console.log(`Available sections: ${err.availableSections.join(', ')}`);
|
|
@@ -334,8 +644,13 @@ async function handleLookup(section, asOfDate) {
|
|
|
334
644
|
|
|
335
645
|
async function handleList() {
|
|
336
646
|
try {
|
|
337
|
-
console.log('\n[INFO] Fetching indexed statutory sections from MudraForge Commons...');
|
|
338
647
|
const sections = await listSections();
|
|
648
|
+
if (isJsonMode) {
|
|
649
|
+
console.log(JSON.stringify({ sections, total: sections.length }, null, 2));
|
|
650
|
+
process.exit(0);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
console.log('\n[INFO] Fetching indexed statutory sections from MudraForge Commons...');
|
|
339
654
|
console.log('───────────────────────────────────────────────────────────────────────────');
|
|
340
655
|
for (const s of sections) {
|
|
341
656
|
const displayValidTo = (s.validTo === 'OPEN_ENDED' || s.validTo === '2099-12-31') ? 'In Force' : s.validTo;
|
|
@@ -343,6 +658,10 @@ async function handleList() {
|
|
|
343
658
|
}
|
|
344
659
|
console.log('───────────────────────────────────────────────────────────────────────────\n');
|
|
345
660
|
} catch (err) {
|
|
661
|
+
if (isJsonMode) {
|
|
662
|
+
console.error(JSON.stringify({ status: 'ERROR', error: err.message }, null, 2));
|
|
663
|
+
process.exit(1);
|
|
664
|
+
}
|
|
346
665
|
console.error(`[ERROR] Failed to list sections: ${err.message}`);
|
|
347
666
|
process.exit(1);
|
|
348
667
|
}
|
|
@@ -350,6 +669,11 @@ async function handleList() {
|
|
|
350
669
|
|
|
351
670
|
function handleDecodeId(rawId) {
|
|
352
671
|
const res = decodeTaxIdentifier(rawId);
|
|
672
|
+
if (isJsonMode) {
|
|
673
|
+
console.log(JSON.stringify(res, null, 2));
|
|
674
|
+
process.exit(res.isValid ? 0 : 1);
|
|
675
|
+
}
|
|
676
|
+
|
|
353
677
|
console.log('\n===========================================================================');
|
|
354
678
|
console.log('INDIAN TAX IDENTIFIER DECODER & OFFLINE MOD-36 VALIDATOR');
|
|
355
679
|
console.log('===========================================================================');
|
|
@@ -382,6 +706,10 @@ Usage:
|
|
|
382
706
|
npx @mudraforge/statutory <command> [options]
|
|
383
707
|
|
|
384
708
|
Commands:
|
|
709
|
+
doctor [--offline] Run full diagnostic check of runtime, substrate integrity, & edge status
|
|
710
|
+
verify [path] Cryptographically verify OKF substrate authenticity & file integrity
|
|
711
|
+
crosswalk <section> Dual-act 1961 <-> 2025 bridge (earlier of credit vs payment)
|
|
712
|
+
diff Semantic regulatory changelog (--section, --since, --domain)
|
|
385
713
|
browse Interactively browse local OKF statutory filesystem
|
|
386
714
|
lookup <section> [date] Look up section details & temporal status (e.g. 194Q 2025-06-01)
|
|
387
715
|
decode-id <pan_or_gstin> Offline ISO Mod-36 checksum, state name, and PAN entity decoding
|
|
@@ -390,6 +718,21 @@ Commands:
|
|
|
390
718
|
list List all indexed statutory sections
|
|
391
719
|
init-skill [path] Install SKILL.md into your agent workspace (defaults to .agents/skills/)
|
|
392
720
|
help Show this help message
|
|
721
|
+
|
|
722
|
+
Options:
|
|
723
|
+
--credit <YYYY-MM-DD> Date of credit to vendor/payee account in books (used in crosswalk)
|
|
724
|
+
--payment <YYYY-MM-DD> Date of actual payment/disbursement (used in crosswalk)
|
|
725
|
+
--since <YYYY-MM-DD> Filter amendments taking effect on or after date (used in diff)
|
|
726
|
+
--section, -s <section> Filter amendments by section code (used in diff)
|
|
727
|
+
--domain <domain> Filter amendments by statutory domain (used in diff)
|
|
728
|
+
--json, -j Emit clean, machine-parseable JSON to stdout (suppresses banners)
|
|
729
|
+
--offline, -o Bypass outbound network checks (air-gapped / offline environment mode)
|
|
730
|
+
|
|
731
|
+
Exit Code Conventions:
|
|
732
|
+
0 Success / Verification passed
|
|
733
|
+
1 Usage or operational error (invalid syntax / parameters)
|
|
734
|
+
2 Cryptographic authenticity or integrity violation (mismatch/revocation)
|
|
735
|
+
3 Statutory policy violation (e.g. missing baseline provision)
|
|
393
736
|
`);
|
|
394
737
|
}
|
|
395
738
|
|