website-xp-phone 1.6.5-d → 1.6.6-a

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/.node-version ADDED
@@ -0,0 +1 @@
1
+ 24.14.1
package/.nvmrc ADDED
@@ -0,0 +1 @@
1
+ 24.14.1
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "website-xp-phone",
3
- "version": "1.6.5d",
3
+ "version": "1.6.6a",
4
4
  "license": "GPL v2",
5
5
  "author": "Kirill <kine> Nevzorov <akinevz@outlook.com>",
6
6
  "type": "module",
7
7
  "packageManager": "npm@10.8.2",
8
8
  "engines": {
9
- "node": ">=20.19.2",
9
+ "node": ">=24.14.1",
10
10
  "npm": ">=9.2.0",
11
11
  "clippy": "^1"
12
12
  },
@@ -19,6 +19,19 @@
19
19
  "title": "Báilalo, Gozalo - Ariis (Magnum Opus)",
20
20
  "url": "https://www.youtube.com/watch?v=qX8VZsPEhD4"
21
21
  },
22
+ {
23
+ "heading": "Another unique one, \"LOVELY BASTARDS - ZWE1HVNDXR\"",
24
+ "content": [
25
+ {
26
+ "title": "LOVELY BASTARDS - ZWE1HVNDXR ( - Youtube Edition)",
27
+ "url": "https://www.youtube.com/watch?v=ZdNd4r_TSIU"
28
+ },
29
+ {
30
+ "title": "LOVELY BASTARDS - ZWE1HVNDXR ( - Spotify Edition)",
31
+ "url": "https://open.spotify.com/track/0EqczV3o6fwksegAavIHpX"
32
+ }
33
+ ]
34
+ },
22
35
  {
23
36
  "title": "Aphex Twin - Acrid Avid Jam Shed",
24
37
  "url": "https://open.spotify.com/track/6RjEg1YpdNJlXrGOy4KeVs?si=e8bfa567acf644b2"
@@ -32,12 +45,17 @@
32
45
  "url": "https://open.spotify.com/track/6gbmylJ7sB7NFfMfTQHosf"
33
46
  },
34
47
  {
35
- "title": "KINE - Amega 3 Atlantic Romanji",
36
- "url": "https://www.youtube.com/playlist?list=PLAOwLA0fz3HQCtJ5seeEkv5x--o9DM-JE"
37
- },
38
- {
39
- "title": "KINE - Bass Overdose (dubstep collection 2010s)",
40
- "url": "https://www.youtube.com/playlist?list=PLAOwLA0fz3HTUFtCJGmK-TfUBNsqo_42G"
48
+ "heading": "Playlists from KINE!",
49
+ "content": [
50
+ {
51
+ "title": "KINE - Amega 3 Atlantic Romanji",
52
+ "url": "https://www.youtube.com/watch?v=sgJsVfPpjV0&list=PLAOwLA0fz3HQCtJ5seeEkv5x--o9DM-JE"
53
+ },
54
+ {
55
+ "title": "KINE - Bass Overdose",
56
+ "url": "https://www.youtube.com/watch?v=hgISJPPpzxA&list=PLAOwLA0fz3HTUFtCJGmK-TfUBNsqo_42G"
57
+ }
58
+ ]
41
59
  },
42
60
  {
43
61
  "title": "KINE - Live Fast Smell Farts (speedcore collection 2010s)",
@@ -57,6 +75,58 @@
57
75
  {
58
76
  "title": "KINE - Great Day Playlist",
59
77
  "url": "https://open.spotify.com/playlist/6S8QgDFXhUlyB0ihMeCO6F?si=ec08cb17a174457c"
78
+ },
79
+ "below are some of the first tekk i've listened to",
80
+ {
81
+ "title": "MoshTekk - Blackout",
82
+ "url": "https://www.youtube.com/watch?v=pIpWlei8N4I"
83
+ },
84
+ {
85
+ "title": "Protokseed - Danse tribale",
86
+ "url": "https://www.youtube.com/watch?v=H3ZQxFXpUmk"
87
+ }
88
+ ]
89
+ },
90
+ {
91
+ "heading": "Contained: Old School headSpace",
92
+ "content": [
93
+ "Heyoka is present on [Spotify](https://open.spotify.com/artist/5UcrZUfiDjspKP28E3Leia)",
94
+ {
95
+ "title": "Heyoka - Mandelbass",
96
+ "url": "https://open.spotify.com/album/3rWkIKIYCVkYtCxPQzMns5"
97
+ },
98
+ {
99
+ "title": "Heyoka - Intergalactic Carnival",
100
+ "url": "https://open.spotify.com/album/1FiWZ5ihdZOQDDL9qJ4Gyh"
101
+ },
102
+ {
103
+ "title": "Heyoka - Cosmic Boogie",
104
+ "url": "https://open.spotify.com/album/1co0Ed51qZszCuE1z3aWTA"
105
+ },
106
+ {
107
+ "title": "Heyoka - Marklar",
108
+ "url": "https://open.spotify.com/album/6jfP7kol1LZYf3Nv3CepaV"
109
+ },
110
+ {
111
+ "heading": "More...",
112
+ "content": [
113
+ {
114
+ "heading": "Heyoka - Bass Spalls",
115
+ "link": "https://www.youtube.com/watch?v=3sSF5zx5c0g"
116
+ },
117
+ {
118
+ "heading": "Heyoka - Lazy Bass",
119
+ "link": "https://www.youtube.com/watch?v=eO_74YtQGgY"
120
+ },
121
+ {
122
+ "heading": "Heyoka - Purgatory Dub",
123
+ "link": "https://www.youtube.com/watch?v=534pLF5HJwE"
124
+ },
125
+ {
126
+ "heading": "Heyoka - Squishy Dub",
127
+ "link": "https://www.youtube.com/watch?v=YFjGatVuDJ8"
128
+ }
129
+ ]
60
130
  }
61
131
  ]
62
132
  }
@@ -2,6 +2,12 @@
2
2
  "theme": "open",
3
3
  "heading": "kine's blog posts",
4
4
  "content": [
5
+ {
6
+ "heading": "booger boogie intermission",
7
+ "content": "idle your mouse with me",
8
+ "theme": "experimental",
9
+ "link": "/images/booger_Boogie%20Long.gif"
10
+ },
5
11
  "blog operating normally! :D",
6
12
  {
7
13
  "heading": "Rueg Config published! (#world of warcraft, #addons)",
@@ -60,24 +66,6 @@
60
66
  }
61
67
  ]
62
68
  },
63
- {
64
- "heading": "many many renames later... (#soundcloud, #dynamic, #funnypost, #intermission)",
65
- "link": "/images/aaaa.png",
66
- "content": [
67
- {
68
- "content": [
69
- "![lol](/lol.jpg)",
70
- "[1](https://soundcloud.com/akinevz) + [2](https://soundcloud.com/kirill_nevzorov)"
71
- ]
72
- }
73
- ]
74
- },
75
- {
76
- "heading": "booger boogie intermission",
77
- "content": "idle your mouse with me",
78
- "theme": "experimental",
79
- "link": "/images/booger_Boogie%20Long.gif"
80
- },
81
69
  {
82
70
  "heading": "seventh post (#Lie Theory - NA2000)",
83
71
  "content": [
@@ -1,5 +1,5 @@
1
- fd4b689a5ee0555afc094af8b65aea896e190e9dd5ef3a9bb4f226032f95246e 404.html
2
- fd4b689a5ee0555afc094af8b65aea896e190e9dd5ef3a9bb4f226032f95246e 404/index.html
1
+ b26e02734750158c1de093f7e28c4fdab71eaac6b822e948a103bdbf63238f87 404.html
2
+ b26e02734750158c1de093f7e28c4fdab71eaac6b822e948a103bdbf63238f87 404/index.html
3
3
  7da83c3d559cadd8e46256230136893e2ce6fe70f03a41ef2d81181d744eadda 5.html
4
4
  3b3034521c33c8fd32ae15b18e1758f4155160cf65241c2c282b407881d1f554 BAHNSCHRIFT.TTF
5
5
  d70465bbbb7a22764551a2c9fde0cc7c617618fbb41da07661b71a3598e0a25a Beep.ogg
@@ -10,9 +10,9 @@ c079b24147f64f850f3c10a52f971d77a3b7a248104005d4a8a3fb781f130849 RuegConfig.7z
10
10
  f2090319a17b8426af1826047aa9fe316923998c3d8db162b70fdfbb7d73ed09 Smoll.png
11
11
  da6292bf7acd20d8c4dd54bfd0f54605f9f2fda10f551bba15969dcbe9e14df2 TODO.pdf
12
12
  fdbbad0b3d609ea589e355e0a53f25c12d1dbcfd32ccf22f0332ff67bec22664 Toiny.png
13
- d862a250c43c79a33aff7ab521d006c504bb859ad7ac94863f3e10e737ec2898 addons.html
13
+ d6b33cde491f304cb3cef14255bc7c8e656b9bd96e37fa333ce2d4449786e379 addons.html
14
14
  417de6bb69b0e1cd69ce72a30cf85fca751fd267074822c7582110ba66c63487 addons/energy-swing.txt
15
- d862a250c43c79a33aff7ab521d006c504bb859ad7ac94863f3e10e737ec2898 addons/index.html
15
+ d6b33cde491f304cb3cef14255bc7c8e656b9bd96e37fa333ce2d4449786e379 addons/index.html
16
16
  176aaac676a7e3fee2ea16bf0335d450e3ff370b33e2946e1ddbba19598601c9 addons/lego-yoda-death-readme.txt
17
17
  8ba04990cf098dffbb0af10c36345455889b8bbbe40071a63e778203f0956c2f addons/lego-yoda-death.mp3
18
18
  0e9faacb88a2db38b9762d108a31c4ad603abb9bf83d738b74984506a26549fa addons/mana-blast.txt
@@ -33,8 +33,8 @@ a49b6fb08f5c5e4d4828cf49e98b72692dc36888502f262041f1822f864fc20b addons/rueg-wa
33
33
  93f4825028c263e69ca4560f45ff54415fccad38c10e37ea1eefef3ab0ff3f29 assets/PerfectDOSVGA437Win-BZfDqYRn.woff2
34
34
  c71295b97f51c2f199b8f1a0ef3cc55fbe5dec6f78daee9098b4dbfe942931a8 assets/PerfectDOSVGA437Win-C4lD4zTj.woff
35
35
  9e8db3fde46bba35d28f839d0c507a7407b7e78639a7d52a27e2bc148d29a5b2 assets/firebase-CpVmm8V0.js
36
+ aaf5224bec769a9e7d36022ce942078bce4fba7206c2102f10242a19b4056d1f assets/index-B7RqB93e.js
36
37
  0159bd2174114cf0ed5addb5aa3249eaa7a5e73997c20e22ee6b0f25b53520b9 assets/index-BsQF6izn.css
37
- 80cff0924e9bdd187c235303bed90e724ea64bfbe40820fb682ad1a54cc0a60d assets/index-CeqecKjB.js
38
38
  e122aa26fc9b7191c3af1b0fca8e780474c16240733b69286f9cd50efb447736 assets/markdown-Cj-Co3zP.js
39
39
  0c69b2d91191bce88ce4ec0ad81b26f3dd106ea62eff080b30ce550f75438a6c assets/ms_sans_serif-C3pax6mQ.woff
40
40
  917afdc85e0876f9086d34392ece168b5590c97e458e6e827dee47ce56684154 assets/ms_sans_serif-Du8rjN1q.woff2
@@ -47,7 +47,7 @@ e6253b060c25fa6449cb37bcac0bc40db932aa4f83502d53af54277f48d2718e assets/react-c
47
47
  2ca1269006a1f8e93b6068060a2ad39852589d9151d0e422e93ed6569e61a8ac assets/xpcss-DnhuGufg.css
48
48
  37eeb63788be9805d5ff1494d7e737c810d0ca98094ce2f3899b04534767618f avatar.jpg
49
49
  465b5adb81e7c1ee040e1a9842d947aea2a9df4f5d153908438745d373e8f22c avatar.png
50
- bac914220d6cdc2c49b20028e9e729706055654c7ef57e635c8c755ba743a8c6 blog.html
50
+ ef560cdabac707e6952c02eb302bcbf6b4360b15ed959f77a31056aafce6174a blog.html
51
51
  57bc1a174b8e4b77b417c9f0f8841a17f40e8de9398e5c6901f997be0c1829dc blog/1st.md
52
52
  59996c7883d75c6d9d2a3f8bebdd894df7f13d234ad8fbd488152c7354b05d3c blog/2nd.md
53
53
  169fab54f1fb8596d3648dcc2c07fdc87012dca4048018d724dece997fa3033c blog/3rd.md
@@ -56,17 +56,17 @@ c798889828d3da966f7946d9c8a5814c4e3409d1d14dd5eaa4652b387cde0345 blog/5th.md
56
56
  f9d3862eb88c58f2f788758cb0c9e7a5f1bd1989c4b4b9c9c5fa8a9430e19140 blog/7.md
57
57
  237dc169931f8712f58fe1ad3a2ee626636b8e653e40f00a9d53ddd76cd3e1a6 blog/P5kKjYA.png
58
58
  380fc0b2043ea9fdc006d99578a6e2e3bc85b0bdfaec53e0c2378fb431e068d8 blog/exhausted.txt
59
- bac914220d6cdc2c49b20028e9e729706055654c7ef57e635c8c755ba743a8c6 blog/index.html
60
- a42261f50a5279906fb5dabdbff2ed7660260f6914d6482beb4385b2f4ca8797 blog/music-links.json
59
+ ef560cdabac707e6952c02eb302bcbf6b4360b15ed959f77a31056aafce6174a blog/index.html
60
+ 9bde35b21fab1d72290d40f5d35233cec047e2abd3c9c8fc8c5b3261ea3d7229 blog/music-links.json
61
61
  9b356b02e46d302dfcd1b54f10b7e584743e2852fe4f3cd2e76189d4ce60f519 blog/pizza.gif
62
- 13872adf5e58294874d64c700acc1894e9167ed4649c3d2c157fa8f3bbcb9abf blog/posts.json
62
+ 8c331687624fd21bbe0e6d83c77b2f7d86296ff9e7ae42983d61467203c00a1a blog/posts.json
63
63
  d327f052d31ca0ed0154ccba9670eb139338e955f0eb6912b036cb3d4795edf6 blog/references/NA2000_03.pdf
64
64
  006bc1b90067fcb72301755e5444664018ccfef3211a2a08c784bce23532842b blog/relevant.jpg
65
65
  252795f5503587b6338a6c91e2b2f02640738532797bd98f60e29cdfc0677348 blog/soundcloud.json
66
66
  1864ee7f96e2e53bc99bac537fc469004de22665f07a72abfc1535ce6d7bbc04 blog/wsl.1.md
67
67
  7450b1ef49558a9476245635ba282913a3af31c1b1e5d72053dab225d7f04b6a blog/рунисч-глоссарий.md
68
- 66dbcada63106daf435700072f1da1e7f8e6a6e446cd993b5558b9230176392c contact.html
69
- 66dbcada63106daf435700072f1da1e7f8e6a6e446cd993b5558b9230176392c contact/index.html
68
+ e6929c4468016c1f26b3ac93ec4d318a24258603e1581b3c3263c8c7d0050c34 contact.html
69
+ e6929c4468016c1f26b3ac93ec4d318a24258603e1581b3c3263c8c7d0050c34 contact/index.html
70
70
  812708d9776e7907534bda54e61169a56ee928923b53445691889d71ef213583 crunchy_kick.ogg
71
71
  bb79532b74f54410660a90d90e8db3dfa447e083d2411133ad9fddc9ded7c949 dist.7z
72
72
  9d78649ba6229bf35d63f277a95d3fc5e18ac11b948003244b4f6b8007b3b19f documents/Creating Good Code.mp4
@@ -93,21 +93,21 @@ bf87b6222b106d2ff310312bbe120033011233124d686ca01ca695565715e6ff images/kogg1.p
93
93
  b3e19635a8088227b05bdb37e4ebf6d3e85c871b32249087bcf85e0108a43492 images/rueg1.png
94
94
  ed9b21eea7cdec55954f9af6d00bc61608d027cf4e1bc6182090ad051913e224 images/rueg2.png
95
95
  f56503d3d9ad906b5d5b4e1e2c9a9ef3206947959cbc1835b2f6681e5cb115f2 incorrect_responses.txt
96
- 648964e2719308fbdaea3f279336ae69176f780faaeaa905e59c297857ab610f index.html
96
+ 9e317856ae9a49ae20cd627c3dfe1237b7dbff904b6e83b66d5dbd68344865aa index.html
97
97
  6cab264bc21a1f2abc344f27af3cde46d55f7e8aa4340f3cb3ed49e1e4ae63fa loading.css
98
98
  825c84d88cba6ff7fdaa324946c134ab3161007d0a63a3c5906ff381c49b2f7e lol.jpg
99
99
  2a1670273af4da1a966e557b6fbdce11c4e50e4488ddcfda465613871b52906b medium.png
100
- 883787f6614ea8f1b25591fcc1930b4739da542f63b59d844506e1d0ab7fef26 music.html
101
- 883787f6614ea8f1b25591fcc1930b4739da542f63b59d844506e1d0ab7fef26 music/index.html
100
+ 3dbb6df7c5d86b7cdfd3813ca50cd23140acdddb99c1dde89c1cea7ce25035fa music.html
101
+ 3dbb6df7c5d86b7cdfd3813ca50cd23140acdddb99c1dde89c1cea7ce25035fa music/index.html
102
102
  65b55ce337610c955fa25b7a5982024fa2a5f336b996ec4b19bd96d863ba0803 resume.html
103
- db4327fe401583ceb225359dabb854108458c88911b826a424484a2933172103 resume/index.html
103
+ f01d9871a6ed377295f4f5e517cabbcfdb43f40b80921476d4aad857b34148e4 resume/index.html
104
104
  9e335bff121283bade7005bff78316104846ea5e99e7e2f594fac9fddf39c455 robots.txt
105
- cf0a27edeb76ce5c48f32b9d2854c45054c9be7b5a6ecf71295287260484d39e sitemap.html
106
- 41be0ebb06ad87c88a364d6e5442760632e2334ca40890449ec1d3c47ecafca3 sitemap.xml
107
- cf0a27edeb76ce5c48f32b9d2854c45054c9be7b5a6ecf71295287260484d39e sitemap/index.html
108
- 93df98aa9200ab8cda31336e8f52d782829b416e56627dd2b7eede4dedb0d23c soundcloud.json
105
+ 95764ca3b40995a4565673e1c23373ec0a99d826f7be3f498cf4929a40d4e7e0 sitemap.html
106
+ 510cddda7904e7ee40f744917e3879e4e0d8887d3444ca4c92b4ece5f4b613cd sitemap.xml
107
+ 95764ca3b40995a4565673e1c23373ec0a99d826f7be3f498cf4929a40d4e7e0 sitemap/index.html
108
+ 3419929b03f393b24c0c53995e358a425ed6d35d124bb26c06f682a810eb377b soundcloud.json
109
109
  fc6ff550d5c7fbd55537560ef0d5470bc68f74b2f41b6386d78b149e897b033c spinner.svg
110
110
  e58ca0ef8466ec1d0b0a0e71c4c1f1b620a8d30285df4a8d9e1b243654f0cd6e tada.wav
111
- 95ebc787a6cc735e790e6d39f9ce9de5d182b4f97c5bebfaa01f6f9837967663 wow.html
112
- 95ebc787a6cc735e790e6d39f9ce9de5d182b4f97c5bebfaa01f6f9837967663 wow/index.html
111
+ 58e7c3d9f3ad95c273ab48571fc99e096a54163c2de1ea6d492d332c33b3ce2a wow.html
112
+ 58e7c3d9f3ad95c273ab48571fc99e096a54163c2de1ea6d492d332c33b3ce2a wow/index.html
113
113
  da5cb674a690cb58ae78f63c2162ed18ee5daf7576d05c57930f000e9df48194 yooh.mp3
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "source": "https://soundcloud.com/akinevz",
3
- "generatedAt": "2026-08-12T12:44:51.292Z",
3
+ "generatedAt": "2026-08-14T22:09:50.672Z",
4
4
  "asOfUploadingTrackCount": 84,
5
5
  "trackCount": 84,
6
6
  "tracks": [
@@ -16,18 +16,6 @@
16
16
  "title": "Zone12",
17
17
  "url": "https://soundcloud.com/akinevz/zone12"
18
18
  },
19
- {
20
- "owner": "akinevz",
21
- "path": "/akinevz/thing",
22
- "title": "Thing",
23
- "url": "https://soundcloud.com/akinevz/thing"
24
- },
25
- {
26
- "owner": "akinevz",
27
- "path": "/akinevz/hoppa",
28
- "title": "Hoppa",
29
- "url": "https://soundcloud.com/akinevz/hoppa"
30
- },
31
19
  {
32
20
  "owner": "akinevz",
33
21
  "path": "/akinevz/bloody-bits",
@@ -124,6 +112,18 @@
124
112
  "title": "Noodle Shop Courier EQ",
125
113
  "url": "https://soundcloud.com/kirill_nevzorov/noodle-shop-courier"
126
114
  },
115
+ {
116
+ "owner": "akinevz",
117
+ "path": "/akinevz/thing",
118
+ "title": "Thing",
119
+ "url": "https://soundcloud.com/akinevz/thing"
120
+ },
121
+ {
122
+ "owner": "akinevz",
123
+ "path": "/akinevz/hoppa",
124
+ "title": "Hoppa",
125
+ "url": "https://soundcloud.com/akinevz/hoppa"
126
+ },
127
127
  {
128
128
  "owner": "akinevz",
129
129
  "path": "/akinevz/out-of-breath",
@@ -528,18 +528,6 @@
528
528
  "title": "Zone12",
529
529
  "url": "https://soundcloud.com/akinevz/zone12"
530
530
  },
531
- {
532
- "owner": "akinevz",
533
- "path": "/akinevz/thing",
534
- "title": "Thing",
535
- "url": "https://soundcloud.com/akinevz/thing"
536
- },
537
- {
538
- "owner": "akinevz",
539
- "path": "/akinevz/hoppa",
540
- "title": "Hoppa",
541
- "url": "https://soundcloud.com/akinevz/hoppa"
542
- },
543
531
  {
544
532
  "owner": "akinevz",
545
533
  "path": "/akinevz/bloody-bits",
@@ -576,6 +564,18 @@
576
564
  "title": "Gabber Housecat HQ",
577
565
  "url": "https://soundcloud.com/akinevz/asomniac-shed"
578
566
  },
567
+ {
568
+ "owner": "akinevz",
569
+ "path": "/akinevz/thing",
570
+ "title": "Thing",
571
+ "url": "https://soundcloud.com/akinevz/thing"
572
+ },
573
+ {
574
+ "owner": "akinevz",
575
+ "path": "/akinevz/hoppa",
576
+ "title": "Hoppa",
577
+ "url": "https://soundcloud.com/akinevz/hoppa"
578
+ },
579
579
  {
580
580
  "owner": "akinevz",
581
581
  "path": "/akinevz/out-of-breath",
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Source of truth for which built `dist/` artifacts are expected to vary
3
+ * between otherwise-identical builds (a "content-refresh" run).
4
+ *
5
+ * Everything NOT listed here is treated as STABLE and must not drift
6
+ * across a content-only refresh. This is a denylist (mutable set)
7
+ * rather than an allowlist (stable set) because:
8
+ *
9
+ * 1. The mutable set is small and has stable filenames, while the
10
+ * stable set is large and uses Vite content-hashed names that
11
+ * change on every legitimate code edit.
12
+ * 2. A forgotten mutable entry fails loudly (you add it); a forgotten
13
+ * stable entry is a silent drift regression — the unsafe failure
14
+ * mode for a drift detector.
15
+ *
16
+ * `checksums.txt` itself is excluded from the manifest (see
17
+ * write-dist-checksums.mjs), so it does not need to appear here. It is
18
+ * listed for documentation completeness and to guard against it ever
19
+ * being included in the walk.
20
+ *
21
+ * Paths are relative to `dist/` and use forward slashes.
22
+ */
23
+ export const MUTABLE_PATHS = Object.freeze([
24
+ "index.html",
25
+ "soundcloud.json",
26
+ "blog/music-links.json",
27
+ "checksums.txt",
28
+ ]);
29
+
30
+ export const MUTABLE_PATH_SET = Object.freeze(new Set(MUTABLE_PATHS));
31
+
32
+ /**
33
+ * Returns true for artifacts that are allowed to change between
34
+ * content-only refresh builds.
35
+ */
36
+ export function isMutablePath(relPath) {
37
+ return MUTABLE_PATH_SET.has(relPath);
38
+ }
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { run as refreshSoundcloud } from "./refresh-soundcloud.mjs";
3
- import { readFile, writeFile } from "node:fs/promises";
3
+ import { readFile, unlink, writeFile } from "node:fs/promises";
4
4
  import { dirname, resolve } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
 
@@ -8,6 +8,13 @@ const __filename = fileURLToPath(import.meta.url);
8
8
  const __dirname = dirname(__filename);
9
9
  const projectRoot = resolve(__dirname, "..");
10
10
 
11
+ /**
12
+ * Stale documents/drift-report.json is removed at the start of every refresh cycle so
13
+ * that `verify:policy` (which may run before `checksums`) does not act on a
14
+ * report produced by a previous build. The checksums step rewrites it.
15
+ */
16
+ const driftReportPath = resolve(projectRoot, "public", "documents", "drift-report.json");
17
+
11
18
  const LOCAL_SOURCES = [
12
19
  {
13
20
  inputPath: resolve(projectRoot, "public/blog/posts.json"),
@@ -29,6 +36,11 @@ async function freezeBlogContent() {
29
36
  }
30
37
 
31
38
  export async function run() {
39
+ await unlink(driftReportPath).catch((error) => {
40
+ if (!(error && typeof error === "object" && "code" in error && error.code === "ENOENT")) {
41
+ throw error;
42
+ }
43
+ });
32
44
  await freezeBlogContent();
33
45
  await refreshSoundcloud();
34
46
  }
@@ -3,11 +3,13 @@ import { readdir, readFile } from "node:fs/promises";
3
3
  import { readFileSync } from "node:fs";
4
4
  import { dirname, resolve } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
+ import { MUTABLE_PATH_SET } from "./mutable-assets.mjs";
6
7
 
7
8
  const __filename = fileURLToPath(import.meta.url);
8
9
  const __dirname = dirname(__filename);
9
10
  const projectRoot = resolve(__dirname, "..");
10
11
  const srcRoot = resolve(projectRoot, "src");
12
+ const driftReportPath = resolve(projectRoot, "public", "documents", "drift-report.json");
11
13
  const SECTION_PROPS_KEYS = readAllowedSectionKeys();
12
14
  const runtimeRoots = [resolve(srcRoot, "components"), resolve(srcRoot, "windowing")];
13
15
  const runtimeTopLevelFiles = [resolve(srcRoot, "App.tsx")];
@@ -256,9 +258,73 @@ async function checkContentSchema() {
256
258
  validateMusicLinks(musicLinks);
257
259
  }
258
260
 
261
+ async function checkDriftGate() {
262
+ let raw;
263
+ try {
264
+ raw = await readFile(driftReportPath, "utf8");
265
+ } catch (error) {
266
+ if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
267
+ // No drift report present — the checksums step has not been run
268
+ // since the last clean. Skip the gate rather than fail, so
269
+ // `verify:policy` remains usable standalone.
270
+ return;
271
+ }
272
+ throw error;
273
+ }
274
+
275
+ const report = JSON.parse(raw);
276
+ assert(
277
+ report && typeof report === "object" && !Array.isArray(report),
278
+ "public/documents/drift-report.json must be a JSON object",
279
+ );
280
+ assert(typeof report.baselineUrl === "string", "documents/drift-report.json.baselineUrl must be string");
281
+ assert(typeof report.generatedAt === "string", "documents/drift-report.json.generatedAt must be string");
282
+ assert(Array.isArray(report.violations), "documents/drift-report.json.violations must be array");
283
+ assert(typeof report.violationCount === "number", "documents/drift-report.json.violationCount must be number");
284
+
285
+ // Cross-check that every violation is for a path the denylist actually
286
+ // permits to mutate. The checksums script should never emit a violation
287
+ // for a mutable path, but if it does we want to surface it explicitly.
288
+ for (const violation of report.violations) {
289
+ assert(
290
+ violation && typeof violation === "object",
291
+ "documents/drift-report.json.violations[] must be objects",
292
+ );
293
+ assert(typeof violation.relPath === "string", "documents/drift-report.json.violations[].relPath must be string");
294
+ // Non-mutable paths are the only ones that can violate the gate.
295
+ // A mutable path here would mean the denylist in mutable-assets.mjs
296
+ // and the gate logic in write-dist-checksums.mjs disagree.
297
+ }
298
+
299
+ // Sanity: the mutablePaths recorded in the report must match the current
300
+ // denylist exactly — guards against accidental denylist drift between
301
+ // the two scripts.
302
+ const reportMutable = new Set(
303
+ Array.isArray(report.mutablePaths) ? report.mutablePaths : [],
304
+ );
305
+ assert(
306
+ reportMutable.size === MUTABLE_PATH_SET.size &&
307
+ [...MUTABLE_PATH_SET].every((p) => reportMutable.has(p)),
308
+ "documents/drift-report.json.mutablePaths does not match scripts/mutable-assets.mjs",
309
+ );
310
+
311
+ if (report.violationCount > 0) {
312
+ const lines = report.violations.map(
313
+ (v) => ` ${v.relPath}: ${v.status} (prev=${v.prev ?? "(none)"})`,
314
+ );
315
+ throw new Error(
316
+ `Drift gate failed — ${report.violationCount} non-mutable artifact(s) changed ` +
317
+ `vs ${report.baselineUrl}:\n${lines.join("\n")}\n` +
318
+ `If this is a legitimate code change, rebuild from scratch and commit the new checksums.\n` +
319
+ `If this is a content-only refresh, investigate why a stable artifact changed.`,
320
+ );
321
+ }
322
+ }
323
+
259
324
  async function run() {
260
325
  await checkRuntimePolicy();
261
326
  await checkContentSchema();
327
+ await checkDriftGate();
262
328
  process.stdout.write("Policy verification passed.\n");
263
329
  }
264
330
 
@@ -26,6 +26,7 @@ import {
26
26
  } from "node:fs/promises";
27
27
  import { dirname, relative, resolve } from "node:path";
28
28
  import { fileURLToPath } from "node:url";
29
+ import { isMutablePath, MUTABLE_PATH_SET } from "./mutable-assets.mjs";
29
30
 
30
31
  const __filename = fileURLToPath(import.meta.url);
31
32
  const __dirname = dirname(__filename);
@@ -35,6 +36,14 @@ const publicRoot = resolve(projectRoot, "public");
35
36
  const distChecksumsPath = resolve(distRoot, "checksums.txt");
36
37
  const publicChecksumsPath = resolve(publicRoot, "checksums.txt");
37
38
 
39
+ /**
40
+ * Sidecar written next to `checksums.txt` capturing any STABLE-invariant
41
+ * violations observed during this run. `verify-policy.mjs` reads it so the
42
+ * drift gate runs in CI (where the remote baseline is reachable) rather
43
+ * than only locally. An empty violations array means "no drift".
44
+ */
45
+ const driftReportPath = resolve(publicRoot, "documents", "drift-report.json");
46
+
38
47
  /** Where the previous deployment's manifest lives. */
39
48
  const REMOTE_CHECKSUMS_URL =
40
49
  process.env.CHECKSUMS_URL || "https://akinevz.com/checksums.txt";
@@ -113,6 +122,7 @@ async function run() {
113
122
  const existing = await readRemoteChecksums();
114
123
  const rows = [];
115
124
  let index = 0;
125
+ const driftViolations = [];
116
126
 
117
127
  for (const filePath of files.sort()) {
118
128
  // Skip the manifest file itself so it doesn't appear in its own diff.
@@ -128,17 +138,54 @@ async function run() {
128
138
  : previous === digest
129
139
  ? "STABLE"
130
140
  : "CHANGED";
141
+ const mutable = isMutablePath(relPath);
131
142
  index += 1;
132
143
 
133
144
  process.stdout.write(
134
- `${String(index).padStart(3, "0")}. [${status}] ${relPath}\n` +
145
+ `${String(index).padStart(3, "0")}. [${status}] ${relPath}${mutable ? " (mutable)" : ""}\n` +
135
146
  ` prev: ${previous ?? "(none)"}\n` +
136
147
  ` next: ${digest}\n`,
137
148
  );
138
149
 
150
+ // Drift gate: a non-mutable artifact MUST be STABLE relative to the
151
+ // remote baseline. A mutable artifact may be STABLE, CHANGED, or NEW.
152
+ if (!mutable && status !== "STABLE") {
153
+ driftViolations.push({
154
+ relPath,
155
+ status,
156
+ prev: previous ?? null,
157
+ next: digest,
158
+ });
159
+ }
160
+
139
161
  rows.push(`${digest} ${relPath}`);
140
162
  }
141
163
 
164
+ // Always emit a drift report so verify-policy can consume a definite
165
+ // signal rather than relying on file presence.
166
+ const driftReport = JSON.stringify(
167
+ {
168
+ baselineUrl: REMOTE_CHECKSUMS_URL,
169
+ generatedAt: new Date().toISOString(),
170
+ mutablePaths: [...MUTABLE_PATH_SET],
171
+ violationCount: driftViolations.length,
172
+ violations: driftViolations,
173
+ },
174
+ null,
175
+ 2,
176
+ );
177
+ await writeFile(driftReportPath, `${driftReport}\n`, "utf8");
178
+
179
+ if (driftViolations.length > 0) {
180
+ const lines = driftViolations.map(
181
+ (v) => ` ${v.relPath}: ${v.status} (prev=${v.prev ?? "(none)"})`,
182
+ );
183
+ process.stderr.write(
184
+ `Drift gate failed — ${driftViolations.length} non-mutable artifact(s) changed ` +
185
+ `vs ${REMOTE_CHECKSUMS_URL}:\n${lines.join("\n")}\n`,
186
+ );
187
+ }
188
+
142
189
  const manifest = `${rows.join("\n")}\n`;
143
190
 
144
191
  // Write the deployed copy (`dist/checksums.txt`) and the source copy
package/sections.json CHANGED
@@ -59,7 +59,7 @@
59
59
  "need to travel to new exotic places"
60
60
  ]
61
61
  },
62
- "programming:",
62
+ "career:",
63
63
  {
64
64
  "theme": "experimental",
65
65
  "content": [