@intentius/behold 0.19.0 → 0.20.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/AGENTS.md +90 -5
- package/README.md +5 -1
- package/demos.json +3 -2
- package/dist/cli.js +1106 -298
- package/example-argo-estate/README.md +8 -8
- package/example-argo-estate/app-a/package.json +2 -2
- package/example-argo-estate/app-b/package.json +2 -2
- package/example-argo-estate/chant.workspace.json +27 -0
- package/example-argo-estate/control-plane/package.json +2 -2
- package/example-argo-estate/package-lock.json +133 -56
- package/example-carve/README.md +14 -14
- package/example-carve/app/package-lock.json +72 -9
- package/example-carve/app/package.json +2 -2
- package/example-carve/legacy-tf/versions.tf +1 -1
- package/example-choudoufu-estate/README.md +4 -4
- package/example-choudoufu-estate/chant.workspace.json +32 -0
- package/example-choudoufu-estate/scripts/choudoufu-up.sh +21 -0
- package/example-flux-estate/README.md +5 -5
- package/example-flux-estate/app-a/package.json +2 -2
- package/example-flux-estate/app-b/package.json +2 -2
- package/example-flux-estate/chant.workspace.json +27 -0
- package/example-flux-estate/control-plane/package.json +2 -2
- package/example-flux-estate/package-lock.json +133 -56
- package/example-k8s/README.md +20 -20
- package/example-k8s/chant.config.ts +1 -1
- package/example-k8s/ops/k3d-apply.op.ts +1 -1
- package/example-k8s/package-lock.json +129 -211
- package/example-k8s/package.json +2 -3
- package/example-terraform-estate/README.md +5 -5
- package/example-terraform-estate/chant.workspace.json +22 -0
- package/example-writes/README.md +5 -5
- package/example-writes/chant.config.ts +1 -1
- package/example-writes/ops/apply.op.ts +1 -1
- package/example-writes/ops/floci.op.ts +2 -2
- package/example-writes/ops/reconcile.op.ts +1 -1
- package/example-writes/package-lock.json +62 -175
- package/example-writes/package.json +2 -3
- package/package.json +6 -6
- package/web/app.js +54 -8
- package/web/behaviour-scale.js +32 -0
- package/web/behaviour-scale.test.js +39 -0
- package/example-choudoufu-estate/.behold.json +0 -8
package/example-writes/README.md
CHANGED
|
@@ -10,7 +10,7 @@ src/bucket.ts one S3 Bucket + a TLS-only policy (the whole "infra")
|
|
|
10
10
|
ops/apply.op.ts ApplyOp "prod-apply" — code → real AWS (aws cloudformation deploy)
|
|
11
11
|
ops/floci.op.ts Op "floci-apply" — code → local Floci (CloudFormation API), no account
|
|
12
12
|
ops/reconcile.op.ts ReconcileOp "prod-reconcile" — cloud → code PR (the Adopt button)
|
|
13
|
-
chant.config.ts lexicons [aws
|
|
13
|
+
chant.config.ts lexicons [aws], environments [prod]
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
The **Sync** button appears because a project declares an `ApplyOp`. A project with
|
|
@@ -19,7 +19,7 @@ no `*.op.ts` (like `../example`) shows no Sync — that's expected, not a bug.
|
|
|
19
19
|
## Creds-free: deploy to a local emulator (`--local`)
|
|
20
20
|
|
|
21
21
|
No AWS account? Serve with `--local` and behold boots a local Floci emulator
|
|
22
|
-
(needs Docker), then the **floci-apply** Op deploys the bucket to it
|
|
22
|
+
(needs Docker), then the **floci-apply** Op deploys the bucket to it, the same
|
|
23
23
|
delegated-write path, zero cloud creds:
|
|
24
24
|
|
|
25
25
|
```sh
|
|
@@ -36,7 +36,7 @@ you Ctrl-C. Everything below uses real AWS instead.
|
|
|
36
36
|
## Prerequisites
|
|
37
37
|
|
|
38
38
|
- Node 20+, and **AWS credentials** in your environment (`aws sts get-caller-identity`
|
|
39
|
-
should work). The apply creates a real S3 bucket via CloudFormation
|
|
39
|
+
should work). The apply creates a real S3 bucket via CloudFormation, a few cents,
|
|
40
40
|
torn down at the end. behold holds no creds; `chant` uses yours on this machine.
|
|
41
41
|
- One edit: S3 bucket names are **globally unique**, so change `BucketName` in
|
|
42
42
|
[`src/bucket.ts`](src/bucket.ts) to something of your own (e.g. include your AWS
|
|
@@ -59,10 +59,10 @@ npm run dev -- serve example-writes --env prod
|
|
|
59
59
|
colour flip.)
|
|
60
60
|
2. In the header, click **Sync**. The now-line streams the Op's phases:
|
|
61
61
|
`▶ chant run prod-apply` → Build → Plan → **Apply** (`aws cloudformation deploy`).
|
|
62
|
-
`prod-apply` is **ungated**, so it applies straight away
|
|
62
|
+
`prod-apply` is **ungated**, so it applies straight away, no approval step.
|
|
63
63
|
3. When it finishes, the `store` node flips **blue → green (managed)**. Click it: the
|
|
64
64
|
inspect panel's **live** section shows the bucket's observed **physical id** and
|
|
65
|
-
status. That's the hydration
|
|
65
|
+
status. That's the hydration, the cloud's view of your declared resource.
|
|
66
66
|
4. Click **Refresh** any time to re-check drift and drop a lanes frame.
|
|
67
67
|
|
|
68
68
|
## What "gated" looks like
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ApplyOp } from "@intentius/chant
|
|
1
|
+
import { ApplyOp } from "@intentius/chant/op";
|
|
2
2
|
|
|
3
3
|
// code → cloud. Additive apply (no gate) runs one-shot on the local executor —
|
|
4
4
|
// behold's Sync button shells `chant run prod-apply`. Target cloudformation;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { Op, phase, build,
|
|
1
|
+
import { Op, phase, build, httpCheck } from "@intentius/chant/op";
|
|
2
|
+
import { awsApply } from "@intentius/chant-lexicon-aws";
|
|
2
3
|
|
|
3
4
|
// Deploy the S3 bucket to the local Floci emulator via the CloudFormation API
|
|
4
5
|
// (awsApply — direct create-or-update + poll, no aws CLI, honours the endpoint).
|
|
@@ -8,7 +9,6 @@ import { Op, phase, build, awsApply, httpCheck } from "@intentius/chant-lexicon-
|
|
|
8
9
|
export default Op({
|
|
9
10
|
name: "floci-apply",
|
|
10
11
|
overview: "S3 bucket → local Floci (CloudFormation API), no cloud account",
|
|
11
|
-
taskQueue: "behold-local",
|
|
12
12
|
phases: [
|
|
13
13
|
phase("Build", [build(".", { script: "build" })]),
|
|
14
14
|
// Stack name = the env ("prod"), so `serve --local --env prod`'s overlay —
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ReconcileOp } from "@intentius/chant
|
|
1
|
+
import { ReconcileOp } from "@intentius/chant/op";
|
|
2
2
|
|
|
3
3
|
// cloud → code. Snapshot live prod, diff vs source, open a PR for drift/orphans.
|
|
4
4
|
// Scoped to chant-owned resources. behold's Adopt button triggers this.
|
|
@@ -6,9 +6,8 @@
|
|
|
6
6
|
"": {
|
|
7
7
|
"name": "behold-example-writes",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@intentius/chant": "^0.
|
|
10
|
-
"@intentius/chant-lexicon-aws": "^0.
|
|
11
|
-
"@intentius/chant-lexicon-temporal": "^0.54.0"
|
|
9
|
+
"@intentius/chant": "^0.95.0",
|
|
10
|
+
"@intentius/chant-lexicon-aws": "^0.95.0"
|
|
12
11
|
}
|
|
13
12
|
},
|
|
14
13
|
"node_modules/@dagrejs/dagre": {
|
|
@@ -443,14 +442,16 @@
|
|
|
443
442
|
}
|
|
444
443
|
},
|
|
445
444
|
"node_modules/@intentius/chant": {
|
|
446
|
-
"version": "0.
|
|
447
|
-
"resolved": "https://registry.npmjs.org/@intentius/chant/-/chant-0.
|
|
448
|
-
"integrity": "sha512-
|
|
445
|
+
"version": "0.95.0",
|
|
446
|
+
"resolved": "https://registry.npmjs.org/@intentius/chant/-/chant-0.95.0.tgz",
|
|
447
|
+
"integrity": "sha512-zhReqEbP7apFyI9bwHATxSKPbc9HK8W2lJvZpib0YNEgSafydm+dd31Hn+7o3AcHhYbAkLpHFQwlFYbM2uVYCg==",
|
|
449
448
|
"license": "Apache-2.0",
|
|
450
449
|
"dependencies": {
|
|
451
450
|
"@dagrejs/dagre": "^3.0.0",
|
|
451
|
+
"ajv": "^8.20.0",
|
|
452
452
|
"esbuild": "^0.28.1",
|
|
453
453
|
"fflate": "^0.8.2",
|
|
454
|
+
"js-yaml": "^4.3.1",
|
|
454
455
|
"picomatch": "^4.0.3",
|
|
455
456
|
"tsx": "^4.0.0",
|
|
456
457
|
"typescript": "^5.5.0",
|
|
@@ -458,130 +459,44 @@
|
|
|
458
459
|
},
|
|
459
460
|
"bin": {
|
|
460
461
|
"chant": "bin/chant"
|
|
462
|
+
},
|
|
463
|
+
"peerDependencies": {
|
|
464
|
+
"vitest": ">=2"
|
|
465
|
+
},
|
|
466
|
+
"peerDependenciesMeta": {
|
|
467
|
+
"vitest": {
|
|
468
|
+
"optional": true
|
|
469
|
+
}
|
|
461
470
|
}
|
|
462
471
|
},
|
|
463
472
|
"node_modules/@intentius/chant-lexicon-aws": {
|
|
464
|
-
"version": "0.
|
|
465
|
-
"resolved": "https://registry.npmjs.org/@intentius/chant-lexicon-aws/-/chant-lexicon-aws-0.
|
|
466
|
-
"integrity": "sha512-
|
|
473
|
+
"version": "0.95.0",
|
|
474
|
+
"resolved": "https://registry.npmjs.org/@intentius/chant-lexicon-aws/-/chant-lexicon-aws-0.95.0.tgz",
|
|
475
|
+
"integrity": "sha512-DHW/cHL4JEU1foEvFK1i2/1Ur4O9meu4dpuyh1o+rnr8DJwW2Ktf7g+84nVDhTjcPVPGE1DROnnQlSuAlu4u8g==",
|
|
467
476
|
"license": "Apache-2.0",
|
|
468
477
|
"dependencies": {
|
|
469
478
|
"fflate": "^0.8.2",
|
|
470
479
|
"js-yaml": "^4.3.1"
|
|
471
480
|
},
|
|
472
481
|
"peerDependencies": {
|
|
473
|
-
"@intentius/chant": "^0.
|
|
482
|
+
"@intentius/chant": "^0.95.0",
|
|
474
483
|
"typescript": "^5.9.3"
|
|
475
484
|
}
|
|
476
485
|
},
|
|
477
|
-
"node_modules
|
|
478
|
-
"version": "
|
|
479
|
-
"resolved": "https://registry.npmjs.org
|
|
480
|
-
"integrity": "sha512-
|
|
481
|
-
"license": "Apache-2.0",
|
|
482
|
-
"dependencies": {
|
|
483
|
-
"@temporalio/common": "^1.17.2",
|
|
484
|
-
"js-yaml": "^4.3.1"
|
|
485
|
-
},
|
|
486
|
-
"peerDependencies": {
|
|
487
|
-
"@intentius/chant": "^0.54.0",
|
|
488
|
-
"typescript": "^5.9.3",
|
|
489
|
-
"zod": "^4.3.6"
|
|
490
|
-
}
|
|
491
|
-
},
|
|
492
|
-
"node_modules/@protobufjs/aspromise": {
|
|
493
|
-
"version": "1.1.2",
|
|
494
|
-
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
|
|
495
|
-
"integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
|
|
496
|
-
"license": "BSD-3-Clause"
|
|
497
|
-
},
|
|
498
|
-
"node_modules/@protobufjs/base64": {
|
|
499
|
-
"version": "1.1.2",
|
|
500
|
-
"resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
|
|
501
|
-
"integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
|
|
502
|
-
"license": "BSD-3-Clause"
|
|
503
|
-
},
|
|
504
|
-
"node_modules/@protobufjs/codegen": {
|
|
505
|
-
"version": "2.0.5",
|
|
506
|
-
"resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz",
|
|
507
|
-
"integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==",
|
|
508
|
-
"license": "BSD-3-Clause"
|
|
509
|
-
},
|
|
510
|
-
"node_modules/@protobufjs/eventemitter": {
|
|
511
|
-
"version": "1.1.1",
|
|
512
|
-
"resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz",
|
|
513
|
-
"integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==",
|
|
514
|
-
"license": "BSD-3-Clause"
|
|
515
|
-
},
|
|
516
|
-
"node_modules/@protobufjs/fetch": {
|
|
517
|
-
"version": "1.1.1",
|
|
518
|
-
"resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz",
|
|
519
|
-
"integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==",
|
|
520
|
-
"license": "BSD-3-Clause",
|
|
521
|
-
"dependencies": {
|
|
522
|
-
"@protobufjs/aspromise": "^1.1.1"
|
|
523
|
-
}
|
|
524
|
-
},
|
|
525
|
-
"node_modules/@protobufjs/float": {
|
|
526
|
-
"version": "1.0.2",
|
|
527
|
-
"resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
|
|
528
|
-
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
|
|
529
|
-
"license": "BSD-3-Clause"
|
|
530
|
-
},
|
|
531
|
-
"node_modules/@protobufjs/path": {
|
|
532
|
-
"version": "1.1.2",
|
|
533
|
-
"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
|
|
534
|
-
"integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
|
|
535
|
-
"license": "BSD-3-Clause"
|
|
536
|
-
},
|
|
537
|
-
"node_modules/@protobufjs/pool": {
|
|
538
|
-
"version": "1.1.0",
|
|
539
|
-
"resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
|
|
540
|
-
"integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
|
|
541
|
-
"license": "BSD-3-Clause"
|
|
542
|
-
},
|
|
543
|
-
"node_modules/@protobufjs/utf8": {
|
|
544
|
-
"version": "1.1.2",
|
|
545
|
-
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.2.tgz",
|
|
546
|
-
"integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==",
|
|
547
|
-
"license": "BSD-3-Clause"
|
|
548
|
-
},
|
|
549
|
-
"node_modules/@temporalio/common": {
|
|
550
|
-
"version": "1.22.0",
|
|
551
|
-
"resolved": "https://registry.npmjs.org/@temporalio/common/-/common-1.22.0.tgz",
|
|
552
|
-
"integrity": "sha512-1NpQpo/y6XU1ULbo/bXgBEhl6qQnOeB79NrsSJdjI38/hfXPom2IYGJcIxOFfsQICQ0zePEKzB9Yyo31KNpIxA==",
|
|
486
|
+
"node_modules/ajv": {
|
|
487
|
+
"version": "8.20.0",
|
|
488
|
+
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
|
|
489
|
+
"integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
|
|
553
490
|
"license": "MIT",
|
|
554
491
|
"dependencies": {
|
|
555
|
-
"
|
|
556
|
-
"
|
|
557
|
-
"
|
|
558
|
-
"
|
|
559
|
-
"proto3-json-serializer": "^2.0.0"
|
|
492
|
+
"fast-deep-equal": "^3.1.3",
|
|
493
|
+
"fast-uri": "^3.0.1",
|
|
494
|
+
"json-schema-traverse": "^1.0.0",
|
|
495
|
+
"require-from-string": "^2.0.2"
|
|
560
496
|
},
|
|
561
|
-
"
|
|
562
|
-
"
|
|
563
|
-
|
|
564
|
-
},
|
|
565
|
-
"node_modules/@temporalio/proto": {
|
|
566
|
-
"version": "1.22.0",
|
|
567
|
-
"resolved": "https://registry.npmjs.org/@temporalio/proto/-/proto-1.22.0.tgz",
|
|
568
|
-
"integrity": "sha512-X7NVa0Z6HK3l9irZR48FKwZ9w9YXetCdF2z2KCIRr0W7Kul64Wt9o5hwvSXShAtrZuCEEHH8WO/ffHTXxJieLg==",
|
|
569
|
-
"license": "MIT",
|
|
570
|
-
"dependencies": {
|
|
571
|
-
"long": "^5.2.3",
|
|
572
|
-
"protobufjs": "^7.6.4"
|
|
573
|
-
},
|
|
574
|
-
"engines": {
|
|
575
|
-
"node": ">= 20.3.0"
|
|
576
|
-
}
|
|
577
|
-
},
|
|
578
|
-
"node_modules/@types/node": {
|
|
579
|
-
"version": "26.2.0",
|
|
580
|
-
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.2.0.tgz",
|
|
581
|
-
"integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==",
|
|
582
|
-
"license": "MIT",
|
|
583
|
-
"dependencies": {
|
|
584
|
-
"undici-types": "~8.3.0"
|
|
497
|
+
"funding": {
|
|
498
|
+
"type": "github",
|
|
499
|
+
"url": "https://github.com/sponsors/epoberezkin"
|
|
585
500
|
}
|
|
586
501
|
},
|
|
587
502
|
"node_modules/argparse": {
|
|
@@ -631,6 +546,28 @@
|
|
|
631
546
|
"@esbuild/win32-x64": "0.28.2"
|
|
632
547
|
}
|
|
633
548
|
},
|
|
549
|
+
"node_modules/fast-deep-equal": {
|
|
550
|
+
"version": "3.1.3",
|
|
551
|
+
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
|
552
|
+
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
|
553
|
+
"license": "MIT"
|
|
554
|
+
},
|
|
555
|
+
"node_modules/fast-uri": {
|
|
556
|
+
"version": "3.1.8",
|
|
557
|
+
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz",
|
|
558
|
+
"integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==",
|
|
559
|
+
"funding": [
|
|
560
|
+
{
|
|
561
|
+
"type": "github",
|
|
562
|
+
"url": "https://github.com/sponsors/fastify"
|
|
563
|
+
},
|
|
564
|
+
{
|
|
565
|
+
"type": "opencollective",
|
|
566
|
+
"url": "https://opencollective.com/fastify"
|
|
567
|
+
}
|
|
568
|
+
],
|
|
569
|
+
"license": "BSD-3-Clause"
|
|
570
|
+
},
|
|
634
571
|
"node_modules/fflate": {
|
|
635
572
|
"version": "0.8.3",
|
|
636
573
|
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz",
|
|
@@ -673,29 +610,11 @@
|
|
|
673
610
|
"js-yaml": "bin/js-yaml.js"
|
|
674
611
|
}
|
|
675
612
|
},
|
|
676
|
-
"node_modules/
|
|
677
|
-
"version": "
|
|
678
|
-
"resolved": "https://registry.npmjs.org/
|
|
679
|
-
"integrity": "sha512-
|
|
680
|
-
"license": "
|
|
681
|
-
},
|
|
682
|
-
"node_modules/ms": {
|
|
683
|
-
"version": "3.0.0-canary.1",
|
|
684
|
-
"resolved": "https://registry.npmjs.org/ms/-/ms-3.0.0-canary.1.tgz",
|
|
685
|
-
"integrity": "sha512-kh8ARjh8rMN7Du2igDRO9QJnqCb2xYTJxyQYK7vJJS4TvLLmsbyhiKpSW+t+y26gyOyMd0riphX0GeWKU3ky5g==",
|
|
686
|
-
"license": "MIT",
|
|
687
|
-
"engines": {
|
|
688
|
-
"node": ">=12.13"
|
|
689
|
-
}
|
|
690
|
-
},
|
|
691
|
-
"node_modules/nexus-rpc": {
|
|
692
|
-
"version": "0.0.2",
|
|
693
|
-
"resolved": "https://registry.npmjs.org/nexus-rpc/-/nexus-rpc-0.0.2.tgz",
|
|
694
|
-
"integrity": "sha512-IWjIExdVYlmwXuzHdY/Q3lXCv1gbqoAXPazQhy2w4Xgtgha3H0OOujEESVPQcFUFMWm+pAk2gKnb57g8S41JZg==",
|
|
695
|
-
"license": "MIT",
|
|
696
|
-
"engines": {
|
|
697
|
-
"node": ">= 20.0.0"
|
|
698
|
-
}
|
|
613
|
+
"node_modules/json-schema-traverse": {
|
|
614
|
+
"version": "1.0.0",
|
|
615
|
+
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
|
616
|
+
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
|
617
|
+
"license": "MIT"
|
|
699
618
|
},
|
|
700
619
|
"node_modules/picomatch": {
|
|
701
620
|
"version": "4.0.5",
|
|
@@ -709,39 +628,13 @@
|
|
|
709
628
|
"url": "https://github.com/sponsors/jonschlinkert"
|
|
710
629
|
}
|
|
711
630
|
},
|
|
712
|
-
"node_modules/
|
|
631
|
+
"node_modules/require-from-string": {
|
|
713
632
|
"version": "2.0.2",
|
|
714
|
-
"resolved": "https://registry.npmjs.org/
|
|
715
|
-
"integrity": "sha512-
|
|
716
|
-
"license": "
|
|
717
|
-
"dependencies": {
|
|
718
|
-
"protobufjs": "^7.2.5"
|
|
719
|
-
},
|
|
720
|
-
"engines": {
|
|
721
|
-
"node": ">=14.0.0"
|
|
722
|
-
}
|
|
723
|
-
},
|
|
724
|
-
"node_modules/protobufjs": {
|
|
725
|
-
"version": "7.6.5",
|
|
726
|
-
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.5.tgz",
|
|
727
|
-
"integrity": "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==",
|
|
728
|
-
"hasInstallScript": true,
|
|
729
|
-
"license": "BSD-3-Clause",
|
|
730
|
-
"dependencies": {
|
|
731
|
-
"@protobufjs/aspromise": "^1.1.2",
|
|
732
|
-
"@protobufjs/base64": "^1.1.2",
|
|
733
|
-
"@protobufjs/codegen": "^2.0.5",
|
|
734
|
-
"@protobufjs/eventemitter": "^1.1.1",
|
|
735
|
-
"@protobufjs/fetch": "^1.1.1",
|
|
736
|
-
"@protobufjs/float": "^1.0.2",
|
|
737
|
-
"@protobufjs/path": "^1.1.2",
|
|
738
|
-
"@protobufjs/pool": "^1.1.0",
|
|
739
|
-
"@protobufjs/utf8": "^1.1.1",
|
|
740
|
-
"@types/node": ">=13.7.0",
|
|
741
|
-
"long": "^5.3.2"
|
|
742
|
-
},
|
|
633
|
+
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
|
634
|
+
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
|
|
635
|
+
"license": "MIT",
|
|
743
636
|
"engines": {
|
|
744
|
-
"node": ">=
|
|
637
|
+
"node": ">=0.10.0"
|
|
745
638
|
}
|
|
746
639
|
},
|
|
747
640
|
"node_modules/tsx": {
|
|
@@ -775,12 +668,6 @@
|
|
|
775
668
|
"node": ">=14.17"
|
|
776
669
|
}
|
|
777
670
|
},
|
|
778
|
-
"node_modules/undici-types": {
|
|
779
|
-
"version": "8.3.0",
|
|
780
|
-
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz",
|
|
781
|
-
"integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==",
|
|
782
|
-
"license": "MIT"
|
|
783
|
-
},
|
|
784
671
|
"node_modules/zod": {
|
|
785
672
|
"version": "4.4.3",
|
|
786
673
|
"resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
|
|
@@ -7,8 +7,7 @@
|
|
|
7
7
|
"build": "./node_modules/.bin/chant build src --lexicon aws -o template.json"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@intentius/chant": "^0.
|
|
11
|
-
"@intentius/chant-lexicon-aws": "^0.
|
|
12
|
-
"@intentius/chant-lexicon-temporal": "^0.54.0"
|
|
10
|
+
"@intentius/chant": "^0.95.0",
|
|
11
|
+
"@intentius/chant-lexicon-aws": "^0.95.0"
|
|
13
12
|
}
|
|
14
13
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/behold",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "behold — a live control plane on chant. See your whole estate (every substrate in one graph), coloured by drift; act through delegated, gated Ops.",
|
|
@@ -41,20 +41,20 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@hono/node-server": "^2.0.5",
|
|
44
|
-
"@intentius/chant": "^0.
|
|
45
|
-
"@intentius/chant-k8s-client": "^0.
|
|
44
|
+
"@intentius/chant": "^0.95.0",
|
|
45
|
+
"@intentius/chant-k8s-client": "^0.95.0",
|
|
46
46
|
"@intentius/pinhole": "^0.3.8",
|
|
47
47
|
"hono": "^4.12.34"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
|
-
"@intentius/chant-lexicon-terraform": "^0.
|
|
51
|
-
"@
|
|
50
|
+
"@intentius/chant-lexicon-terraform": "^0.95.0",
|
|
51
|
+
"@cdktn/hcl2json": "^0.24.0"
|
|
52
52
|
},
|
|
53
53
|
"peerDependenciesMeta": {
|
|
54
54
|
"@intentius/chant-lexicon-terraform": {
|
|
55
55
|
"optional": true
|
|
56
56
|
},
|
|
57
|
-
"@
|
|
57
|
+
"@cdktn/hcl2json": {
|
|
58
58
|
"optional": true
|
|
59
59
|
}
|
|
60
60
|
},
|
package/web/app.js
CHANGED
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
scaleEnds,
|
|
36
36
|
totalsFor,
|
|
37
37
|
totalsText,
|
|
38
|
+
deltaRows,
|
|
38
39
|
} from "./behaviour-scale.js";
|
|
39
40
|
import { addPanelTab, initPanel, setPanelTab, togglePanelCollapsed, isPanelCollapsed } from "./panel.js";
|
|
40
41
|
// #254: the carve walkthrough's stepper — everything it DECIDES is a pure
|
|
@@ -727,6 +728,15 @@ function inspect(node) {
|
|
|
727
728
|
// statement about this entity is legible — and where a figure without its
|
|
728
729
|
// provenance would be visible as the contract violation it is.
|
|
729
730
|
renderBehaviourSection(section, blockOf(node));
|
|
731
|
+
// #402: what the FILE says this entity costs, beside what the account does.
|
|
732
|
+
// On a card the account no longer holds this is the only behaviour row, and
|
|
733
|
+
// it says so rather than reading as the card's cost.
|
|
734
|
+
const declared = node.attrs && node.attrs._behaviourDeclared;
|
|
735
|
+
if (declared && declared.cost) {
|
|
736
|
+
const add = section("behaviour · declared");
|
|
737
|
+
add("cost", figureWithBadge(fmtCost(declared.cost.perHour, declared.cost.currency), [declared], behaviourMeta()));
|
|
738
|
+
if (!blockOf(node)) add("live", "no live figure — the prediction of the account does not include this entity");
|
|
739
|
+
}
|
|
730
740
|
|
|
731
741
|
// Render diff (#146's deferred half): what `chant helm diff <digest> <env>
|
|
732
742
|
// --live --json` said about this chart's PINNED render. Never rely on the
|
|
@@ -858,15 +868,22 @@ function inspect(node) {
|
|
|
858
868
|
const attrKeys = Object.keys(node.attrs || {}).filter((k) => !k.startsWith("_") && !(k === "release" && release));
|
|
859
869
|
if (attrKeys.length) {
|
|
860
870
|
const decl = section("declared");
|
|
861
|
-
// #393 item 10:
|
|
862
|
-
//
|
|
863
|
-
// `adopt_tofu_address`), or its own one-line command
|
|
864
|
-
//
|
|
865
|
-
//
|
|
866
|
-
//
|
|
867
|
-
//
|
|
871
|
+
// #393 item 10: a choudoufu card that can be adopted leads with the write
|
|
872
|
+
// that does it — the two marker tags choudoufu named (`adopt_tofu_estate` /
|
|
873
|
+
// `adopt_tofu_address`), or its own one-line command when it sends one.
|
|
874
|
+
// behold composes neither (src/choudoufu-live.ts); this puts the line
|
|
875
|
+
// first and makes it copyable, the way the moves panel already does for a
|
|
876
|
+
// `live-mv` line. The paragraph explaining the omission follows.
|
|
877
|
+
//
|
|
878
|
+
// #413: both kinds reach here now. An UNOWNED row carries the marker pair,
|
|
879
|
+
// and an adoptable one carries choudoufu's own command — so the tooltip
|
|
880
|
+
// says which of the two this is rather than claiming tags either way.
|
|
868
881
|
const adoptable = node.lexicon === "choudoufu" && typeof node.attrs.adopt === "string" ? "adopt" : null;
|
|
869
|
-
if (adoptable)
|
|
882
|
+
if (adoptable) {
|
|
883
|
+
const isCommand = /\s/.test(node.attrs.adopt.trim()) && !node.attrs.adopt.startsWith("tofu-estate=");
|
|
884
|
+
copyableRow(decl, adoptable, node.attrs.adopt,
|
|
885
|
+
isCommand ? "Copy the command that adopts this object" : "Copy the tags that adopt this object");
|
|
886
|
+
}
|
|
870
887
|
for (const k of attrKeys) {
|
|
871
888
|
if (k === adoptable) continue;
|
|
872
889
|
decl(k, fmtValue(node.attrs[k]));
|
|
@@ -1735,6 +1752,35 @@ function renderBehaviourTotals(host) {
|
|
|
1735
1752
|
// precedence in one place rather than at each call site.
|
|
1736
1753
|
row(b.total ? "estate · engine total" : "estate", all, b.sum, b.total, b.total ? "the engine stated this estate total itself; behold added nothing" : "behold's sum over the engine's per-entity figures");
|
|
1737
1754
|
if (mode === "headroom") host.appendChild(panelMuted("min and median computed by behold over the engine's per-entity figures — no engine states a headroom aggregate."));
|
|
1755
|
+
if (mode === "cost") renderBehaviourDelta(host, b);
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
/**
|
|
1759
|
+
* Live versus declared (#402): what the account costs as it stands, what the
|
|
1760
|
+
* file says it should, and the difference. Present only when the overlay was
|
|
1761
|
+
* asked for at a traffic level, because both figures are predictions *at* one.
|
|
1762
|
+
* The wording of every row is `deltaRows`' (web/behaviour-scale.js).
|
|
1763
|
+
*/
|
|
1764
|
+
function renderBehaviourDelta(host, b) {
|
|
1765
|
+
const rows = deltaRows(b);
|
|
1766
|
+
if (!rows.length) return;
|
|
1767
|
+
host.appendChild(panelHeading(`live versus declared${b.at ? ` · at ${b.at.traffic}` : ""}`));
|
|
1768
|
+
for (const t of rows) {
|
|
1769
|
+
const r = document.createElement("div");
|
|
1770
|
+
r.className = "behaviour-total";
|
|
1771
|
+
const name = document.createElement("div");
|
|
1772
|
+
name.className = "grow";
|
|
1773
|
+
name.style.fontWeight = "600";
|
|
1774
|
+
name.textContent = t.label;
|
|
1775
|
+
r.appendChild(name);
|
|
1776
|
+
for (const [word, text] of [["live", t.live], ["declared", t.declared], ["delta", t.delta]]) {
|
|
1777
|
+
const line = document.createElement("div");
|
|
1778
|
+
line.textContent = `${word} ${text}`;
|
|
1779
|
+
r.appendChild(line);
|
|
1780
|
+
}
|
|
1781
|
+
host.appendChild(r);
|
|
1782
|
+
}
|
|
1783
|
+
host.appendChild(panelMuted("delta is live minus declared. A negative one means the account holds less than the file declares."));
|
|
1738
1784
|
}
|
|
1739
1785
|
|
|
1740
1786
|
function renderPanelScope() {
|
package/web/behaviour-scale.js
CHANGED
|
@@ -199,6 +199,38 @@ export function fmtCost(perHour, currency) {
|
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
/** A headroom fraction as the percentage of capacity still free. */
|
|
202
|
+
/** A signed delta, in the engine's own currency: the sign is the statement. */
|
|
203
|
+
export function fmtDelta(perHour, currency) {
|
|
204
|
+
const figure = fmtCost(Math.abs(perHour), currency);
|
|
205
|
+
if (perHour === 0) return `${figure}, no difference`;
|
|
206
|
+
return `${perHour > 0 ? "+" : "-"}${figure}`;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Live versus declared (#402), as the rows the Scope panel prints: one triple
|
|
211
|
+
* for the estate, one per member that has a side. Empty when the payload
|
|
212
|
+
* carries neither figure, which is a request that named no traffic level.
|
|
213
|
+
*
|
|
214
|
+
* Each figure says whose it is. `total` is the engine's own statement and
|
|
215
|
+
* `sum` is behold adding the engine's per-entity figures; the delta is always
|
|
216
|
+
* behold's subtraction of the two beside it, and a missing side is said, not
|
|
217
|
+
* left blank, so a reader never takes an absent live figure for a zero.
|
|
218
|
+
*/
|
|
219
|
+
export function deltaRows(meta) {
|
|
220
|
+
if (!meta || (!meta.live && !meta.declared)) return [];
|
|
221
|
+
const whose = (f) => (f.basis === "total" ? "engine total" : "behold's sum");
|
|
222
|
+
const side = (f, missing) => (f ? `${fmtCost(f.perHour, f.currency)} · ${whose(f)}` : missing);
|
|
223
|
+
const triple = (label, t) => ({
|
|
224
|
+
label,
|
|
225
|
+
live: side(t.live, "no live figure"),
|
|
226
|
+
declared: side(t.declared, "no declared figure"),
|
|
227
|
+
delta: t.delta ? fmtDelta(t.delta.perHour, t.delta.currency) : "no delta, see diagnostics",
|
|
228
|
+
});
|
|
229
|
+
const rows = [triple("estate", meta)];
|
|
230
|
+
for (const [name, t] of Object.entries(meta.members || {}).sort(([a], [b]) => a.localeCompare(b))) rows.push(triple(name, t));
|
|
231
|
+
return rows;
|
|
232
|
+
}
|
|
233
|
+
|
|
202
234
|
export function fmtHeadroom(v) {
|
|
203
235
|
return typeof v === "number" && Number.isFinite(v) ? `${Math.round(v * 100)}% free` : "—";
|
|
204
236
|
}
|
|
@@ -9,6 +9,8 @@ import { describe, it, expect } from "vitest";
|
|
|
9
9
|
import { THEMES } from "./themes.js";
|
|
10
10
|
import { tokensFor, hexToOklch } from "./theme.js";
|
|
11
11
|
import {
|
|
12
|
+
deltaRows,
|
|
13
|
+
fmtDelta,
|
|
12
14
|
badgeFor,
|
|
13
15
|
blockOf,
|
|
14
16
|
countFigures,
|
|
@@ -305,3 +307,40 @@ describe("what a refusal and an absence do to the modes (#401)", () => {
|
|
|
305
307
|
expect(a.refusal).toBeNull();
|
|
306
308
|
});
|
|
307
309
|
});
|
|
310
|
+
|
|
311
|
+
describe("live versus declared rows (#402)", () => {
|
|
312
|
+
const live = { perHour: 0.005, currency: "USD", basis: "sum" };
|
|
313
|
+
const declared = { perHour: 0.01, currency: "USD", basis: "sum" };
|
|
314
|
+
|
|
315
|
+
it("signs the delta, and says when there is none", () => {
|
|
316
|
+
expect(fmtDelta(-0.005, "USD")).toBe("-0.0050 USD/h");
|
|
317
|
+
expect(fmtDelta(1.5, "USD")).toBe("+1.50 USD/h");
|
|
318
|
+
expect(fmtDelta(0, "USD")).toBe("0.0000 USD/h, no difference");
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it("is empty for a payload with neither figure: no traffic level was named", () => {
|
|
322
|
+
expect(deltaRows(null)).toEqual([]);
|
|
323
|
+
expect(deltaRows({ engine: "e", sum: { perHour: 1, currency: "USD", priced: 1, unpriced: 0 } })).toEqual([]);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it("prints the estate, then each member by name, and says whose each figure is", () => {
|
|
327
|
+
const rows = deltaRows({
|
|
328
|
+
live,
|
|
329
|
+
declared,
|
|
330
|
+
delta: { perHour: -0.005, currency: "USD" },
|
|
331
|
+
members: {
|
|
332
|
+
"team-a": { live: { perHour: 0, currency: "USD", basis: "total" }, declared: { perHour: 0, currency: "USD", basis: "total" }, delta: { perHour: 0, currency: "USD" } },
|
|
333
|
+
monolith: { live: { ...live, basis: "total" }, declared: { ...declared, basis: "total" }, delta: { perHour: -0.005, currency: "USD" } },
|
|
334
|
+
},
|
|
335
|
+
});
|
|
336
|
+
expect(rows.map((r) => r.label)).toEqual(["estate", "monolith", "team-a"]);
|
|
337
|
+
expect(rows[0]).toEqual({ label: "estate", live: "0.0050 USD/h · behold's sum", declared: "0.0100 USD/h · behold's sum", delta: "-0.0050 USD/h" });
|
|
338
|
+
expect(rows[1].live).toBe("0.0050 USD/h · engine total");
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it("says a missing side is missing rather than printing a zero", () => {
|
|
342
|
+
const [estate] = deltaRows({ declared });
|
|
343
|
+
expect(estate.live).toBe("no live figure");
|
|
344
|
+
expect(estate.delta).toBe("no delta, see diagnostics");
|
|
345
|
+
});
|
|
346
|
+
});
|