code wiki / hub / nx_nxsite_spec.nx

nx_nxsite_spec.nx source

↩ module page · 413 lines · 16406 B

1// nx_nxsite_spec.nx -- V-PROV-1: sealed NxSiteDeploymentSpec struct + 2// sealed-enum kinds + per-field constants. 3// 4// Per NISHI_SITE_PROVISIONING_CHARTER §3: ONE sealed declarative spec 5// drives all three frontends (CLI / UI / AI) -> one provisioner backend. 6// This module defines the data model; parser (nx_nxsite_parse) reads 7// .nxsite files; validator (nx_nxsite_validate) checks integrity; 8// applier (nx_site_provisioner) executes. 9// 10// COMPOSES (per "avoid duplicate primitives"): 11// nx_syscalls.sys_mmap (struct allocation) 12// Reuses NxVHostTable + nx_pages_config conventions where applicable 13// 14// COMPOSED BY: 15// hub/nx_nxsite_parse.nx (line-walker fills this struct from .nxsite text) 16// hub/nx_nxsite_validate.nx (checks sealed-enum ranges + cross-section consistency) 17// hub/nx_site_provisioner.nx (V-PROV-2; applies sealed spec to live substrate) 18// 19// V-PROV-1 SCOPE: 20// - 9 sealed sections (per charter §3.2): site, dns, tls, content, 21// auth, routes, rate_limit, telemetry, deploy 22// - Sealed enum kinds for cert_kind, content_format, route_handler 23// - String pool storage (caller-allocated; M4 buf-own) 24// - Per-section sub-arrays (DNS records, routes, rate_limit buckets) 25// 26// Status: V-PROV-1. 2026-05-27. 27 28import "nx_syscalls.nx" 29 30// ===== Sealed verdict surface (codes 3500-3519) ================================================= 31const NX_NSPEC_OK: i64 = 0 32const NX_NSPEC_BAD_INPUT: i64 = 3500 33const NX_NSPEC_POOL_FULL: i64 = 3501 34const NX_NSPEC_TOO_MANY_RECORDS: i64 = 3502 35const NX_NSPEC_TOO_MANY_ROUTES: i64 = 3503 36const NX_NSPEC_TOO_MANY_BUCKETS: i64 = 3504 37const NX_NSPEC_VALUE_TOO_LONG: i64 = 3505 38const NX_NSPEC_BAD_ENUM: i64 = 3506 39 40// ===== Named constants (M7) ================================================= 41const NX_NSPEC_STRING_POOL_CAP: i64 = 65536 // 64 KB packed string pool 42const NX_NSPEC_MAX_DNS_RECORDS: i64 = 64 43const NX_NSPEC_MAX_ROUTES: i64 = 32 44const NX_NSPEC_MAX_RATE_BUCKETS: i64 = 16 45const NX_NSPEC_MAX_FIELD_LEN: i64 = 1024 // per-value cap 46const NX_NSPEC_MAX_NAME_LEN: i64 = 255 // hostname per RFC 1035 47const NX_NSPEC_SPEC_VERSION: *u8 = "1.0.0" as *u8 48const NX_NSPEC_SPEC_VERSION_N: i64 = 5 49 50// ===== Sealed enum: TLS cert kind ================================================= 51const NX_NSPEC_CERT_KIND_NONE: i64 = 0 52const NX_NSPEC_CERT_KIND_SOVEREIGN_SELF: i64 = 1 // sovereign self-signed 53const NX_NSPEC_CERT_KIND_SOVEREIGN_MINI_CA: i64 = 2 // sovereign operator-CA-issued (V+2) 54const NX_NSPEC_CERT_KIND_N: i64 = 3 55 56func nx_nspec_cert_kind_name(k: i64) -> *u8 { 57 if k == NX_NSPEC_CERT_KIND_NONE { return "NONE" as *u8 } 58 if k == NX_NSPEC_CERT_KIND_SOVEREIGN_SELF { return "sovereign_self_signed" as *u8 } 59 if k == NX_NSPEC_CERT_KIND_SOVEREIGN_MINI_CA { return "sovereign_mini_ca" as *u8 } 60 return "UNKNOWN" as *u8 61} 62 63// ===== Sealed enum: content format ================================================= 64const NX_NSPEC_CONTENT_NONE: i64 = 0 65const NX_NSPEC_CONTENT_NISHI_PAGE_V1: i64 = 1 66const NX_NSPEC_CONTENT_STATIC_HTML: i64 = 2 67const NX_NSPEC_CONTENT_MIRROR_LAYER: i64 = 3 68const NX_NSPEC_CONTENT_N: i64 = 4 69 70func nx_nspec_content_format_name(f: i64) -> *u8 { 71 if f == NX_NSPEC_CONTENT_NONE { return "NONE" as *u8 } 72 if f == NX_NSPEC_CONTENT_NISHI_PAGE_V1 { return "nishi_page_v1" as *u8 } 73 if f == NX_NSPEC_CONTENT_STATIC_HTML { return "static_html" as *u8 } 74 if f == NX_NSPEC_CONTENT_MIRROR_LAYER { return "mirror_layer" as *u8 } 75 return "UNKNOWN" as *u8 76} 77 78// ===== Sealed enum: route handler kind ================================================= 79const NX_NSPEC_ROUTE_NONE: i64 = 0 80const NX_NSPEC_ROUTE_STATIC: i64 = 1 81const NX_NSPEC_ROUTE_WIKI_DAEMON: i64 = 2 82const NX_NSPEC_ROUTE_SEARCH_HANDLER: i64 = 3 83const NX_NSPEC_ROUTE_ADMIN_PANEL: i64 = 4 84const NX_NSPEC_ROUTE_SITE_API: i64 = 5 85const NX_NSPEC_ROUTE_404: i64 = 6 86const NX_NSPEC_ROUTE_N: i64 = 7 87 88func nx_nspec_route_handler_name(h: i64) -> *u8 { 89 if h == NX_NSPEC_ROUTE_NONE { return "NONE" as *u8 } 90 if h == NX_NSPEC_ROUTE_STATIC { return "static" as *u8 } 91 if h == NX_NSPEC_ROUTE_WIKI_DAEMON { return "wiki_daemon" as *u8 } 92 if h == NX_NSPEC_ROUTE_SEARCH_HANDLER { return "search_handler" as *u8 } 93 if h == NX_NSPEC_ROUTE_ADMIN_PANEL { return "admin_panel" as *u8 } 94 if h == NX_NSPEC_ROUTE_SITE_API { return "site_api_handler" as *u8 } 95 if h == NX_NSPEC_ROUTE_404 { return "404" as *u8 } 96 return "UNKNOWN" as *u8 97} 98 99// ===== Sealed enum: DNS record type ================================================= 100const NX_NSPEC_DNS_NONE: i64 = 0 101const NX_NSPEC_DNS_A: i64 = 1 102const NX_NSPEC_DNS_AAAA: i64 = 2 103const NX_NSPEC_DNS_MX: i64 = 3 104const NX_NSPEC_DNS_TXT: i64 = 4 105const NX_NSPEC_DNS_CNAME: i64 = 5 106const NX_NSPEC_DNS_NS: i64 = 6 107const NX_NSPEC_DNS_N: i64 = 7 108 109func nx_nspec_dns_kind_name(k: i64) -> *u8 { 110 if k == NX_NSPEC_DNS_A { return "a" as *u8 } 111 if k == NX_NSPEC_DNS_AAAA { return "aaaa" as *u8 } 112 if k == NX_NSPEC_DNS_MX { return "mx" as *u8 } 113 if k == NX_NSPEC_DNS_TXT { return "txt" as *u8 } 114 if k == NX_NSPEC_DNS_CNAME { return "cname" as *u8 } 115 if k == NX_NSPEC_DNS_NS { return "ns" as *u8 } 116 return "UNKNOWN" as *u8 117} 118 119// ===== Sub-structs for per-section repeating items ================================================= 120 121// DNS record: kind + name_off/len + value_off/len + extra_off/len (for MX priority, etc.) 122struct NxNspecDnsRecord { 123 kind: i64 // sealed NX_NSPEC_DNS_* 124 name_off: i64 // into string pool 125 name_len: i64 126 value_off: i64 127 value_len: i64 128 extra_off: i64 // MX priority / TXT etc.; 0 if unused 129 extra_len: i64 130} 131 132const NX_NSPEC_DNS_RECORD_BYTES: i64 = 56 133 134// Route: path prefix + handler kind 135struct NxNspecRoute { 136 prefix_off: i64 // into string pool 137 prefix_len: i64 138 handler: i64 // sealed NX_NSPEC_ROUTE_* 139} 140 141const NX_NSPEC_ROUTE_BYTES: i64 = 24 142 143// Rate limit bucket: prefix + quota + per_seconds + scope (global / per_ip) 144struct NxNspecRateBucket { 145 prefix_off: i64 146 prefix_len: i64 147 quota: i64 // requests 148 per_seconds: i64 // window 149 scope: i64 // 0 = global; 1 = per_ip 150} 151 152const NX_NSPEC_RATE_BUCKET_BYTES: i64 = 40 153 154// ===== Top-level NxSiteDeploymentSpec ================================================= 155// 156// String pool stores all variable-length strings (names, paths, values). 157// Per-field offsets/lengths reference into pool. 158 159struct NxSiteDeploymentSpec { 160 // String pool (M4 caller-allocated) 161 pool: *u8 162 pool_cap: i64 163 pool_used: i64 164 165 // [site] 166 site_name_off: i64 167 site_name_len: i64 168 site_canonical_off: i64 169 site_canonical_len: i64 170 site_description_off: i64 171 site_description_len: i64 172 site_license_off: i64 173 site_license_len: i64 174 site_spec_version_off: i64 175 site_spec_version_len: i64 176 177 // [dns] 178 dns_records: *NxNspecDnsRecord 179 dns_records_cap: i64 180 dns_records_count: i64 181 dns_ns_primary_off: i64 182 dns_ns_primary_len: i64 183 dns_ns_secondary_off: i64 184 dns_ns_secondary_len: i64 185 186 // [tls] 187 tls_cert_kind: i64 // sealed enum 188 tls_signing_key_off: i64 189 tls_signing_key_len: i64 190 tls_cert_validity_days: i64 191 tls_san_off: i64 // comma-separated SANs 192 tls_san_len: i64 193 194 // [content] 195 content_root_off: i64 196 content_root_len: i64 197 content_format: i64 // sealed enum 198 content_index_off: i64 199 content_index_len: i64 200 content_language_off: i64 201 content_language_len: i64 202 203 // [auth] 204 auth_enabled: i64 // 0|1 205 auth_admin_user_off: i64 206 auth_admin_user_len: i64 207 auth_hash_file_off: i64 208 auth_hash_file_len: i64 209 auth_session_max_age: i64 // seconds 210 211 // [routes] 212 routes: *NxNspecRoute 213 routes_cap: i64 214 routes_count: i64 215 216 // [rate_limit] 217 rate_buckets: *NxNspecRateBucket 218 rate_buckets_cap: i64 219 rate_buckets_count: i64 220 221 // [telemetry] 222 tele_enabled: i64 // 0|1 223 tele_retention_days: i64 224 tele_namespace_off: i64 225 tele_namespace_len: i64 226 tele_redact_regex_off: i64 227 tele_redact_regex_len: i64 228 229 // [deploy] 230 deploy_blue_green: i64 // 0|1 231 deploy_signed_by_off: i64 232 deploy_signed_by_len: i64 233 deploy_prev_manifest_off: i64 234 deploy_prev_manifest_len: i64 235 236 valid: i64 237} 238 239// ===== Init ================================================= 240 241func nx_nxsite_spec_init(s: *NxSiteDeploymentSpec, 242 pool: *u8, pool_cap: i64) -> i64 { 243 if (s as i64) == 0 { return 0 - NX_NSPEC_BAD_INPUT } 244 if (pool as i64) == 0 { return 0 - NX_NSPEC_BAD_INPUT } 245 if pool_cap < 4096 { return 0 - NX_NSPEC_BAD_INPUT } 246 if pool_cap > NX_NSPEC_STRING_POOL_CAP { return 0 - NX_NSPEC_POOL_FULL } 247 248 s.pool = pool 249 s.pool_cap = pool_cap 250 s.pool_used = 0 251 252 // Zero all offsets/lengths/enum values 253 s.site_name_off = 0; s.site_name_len = 0 254 s.site_canonical_off = 0; s.site_canonical_len = 0 255 s.site_description_off = 0; s.site_description_len = 0 256 s.site_license_off = 0; s.site_license_len = 0 257 s.site_spec_version_off = 0; s.site_spec_version_len = 0 258 259 s.dns_records = (sys_mmap(NX_NSPEC_MAX_DNS_RECORDS * NX_NSPEC_DNS_RECORD_BYTES)) as *NxNspecDnsRecord 260 s.dns_records_cap = NX_NSPEC_MAX_DNS_RECORDS 261 s.dns_records_count = 0 262 s.dns_ns_primary_off = 0; s.dns_ns_primary_len = 0 263 s.dns_ns_secondary_off = 0; s.dns_ns_secondary_len = 0 264 265 s.tls_cert_kind = NX_NSPEC_CERT_KIND_NONE 266 s.tls_signing_key_off = 0; s.tls_signing_key_len = 0 267 s.tls_cert_validity_days = 0 268 s.tls_san_off = 0; s.tls_san_len = 0 269 270 s.content_root_off = 0; s.content_root_len = 0 271 s.content_format = NX_NSPEC_CONTENT_NONE 272 s.content_index_off = 0; s.content_index_len = 0 273 s.content_language_off = 0; s.content_language_len = 0 274 275 s.auth_enabled = 0 276 s.auth_admin_user_off = 0; s.auth_admin_user_len = 0 277 s.auth_hash_file_off = 0; s.auth_hash_file_len = 0 278 s.auth_session_max_age = 0 279 280 s.routes = (sys_mmap(NX_NSPEC_MAX_ROUTES * NX_NSPEC_ROUTE_BYTES)) as *NxNspecRoute 281 s.routes_cap = NX_NSPEC_MAX_ROUTES 282 s.routes_count = 0 283 284 s.rate_buckets = (sys_mmap(NX_NSPEC_MAX_RATE_BUCKETS * NX_NSPEC_RATE_BUCKET_BYTES)) as *NxNspecRateBucket 285 s.rate_buckets_cap = NX_NSPEC_MAX_RATE_BUCKETS 286 s.rate_buckets_count = 0 287 288 s.tele_enabled = 0 289 s.tele_retention_days = 0 290 s.tele_namespace_off = 0; s.tele_namespace_len = 0 291 s.tele_redact_regex_off = 0; s.tele_redact_regex_len = 0 292 293 s.deploy_blue_green = 0 294 s.deploy_signed_by_off = 0; s.deploy_signed_by_len = 0 295 s.deploy_prev_manifest_off = 0; s.deploy_prev_manifest_len = 0 296 297 s.valid = 1 298 return NX_NSPEC_OK 299} 300 301// ===== Internal: append string to pool; returns offset ================================================= 302 303func nx_nspec_pool_append(s: *NxSiteDeploymentSpec, 304 src: *u8, src_n: i64) -> i64 { 305 if s.valid != 1 { return 0 - NX_NSPEC_BAD_INPUT } 306 if src_n < 0 { return 0 - NX_NSPEC_BAD_INPUT } 307 if src_n > NX_NSPEC_MAX_FIELD_LEN { return 0 - NX_NSPEC_VALUE_TOO_LONG } 308 if s.pool_used + src_n > s.pool_cap { return 0 - NX_NSPEC_POOL_FULL } 309 let off: i64 = s.pool_used 310 var i: i64 = 0 311 while i < src_n { s.pool[off + i] = src[i]; i = i + 1 } 312 s.pool_used = s.pool_used + src_n 313 return off 314} 315 316// ===== Sub-record accessors ================================================= 317 318func nx_nspec_dns_record_at(s: *NxSiteDeploymentSpec, i: i64) -> *NxNspecDnsRecord { 319 if s.valid != 1 { return 0 as *NxNspecDnsRecord } 320 if i < 0 { return 0 as *NxNspecDnsRecord } 321 if i >= s.dns_records_count { return 0 as *NxNspecDnsRecord } 322 return ((s.dns_records as i64) + i * NX_NSPEC_DNS_RECORD_BYTES) as *NxNspecDnsRecord 323} 324 325func nx_nspec_route_at(s: *NxSiteDeploymentSpec, i: i64) -> *NxNspecRoute { 326 if s.valid != 1 { return 0 as *NxNspecRoute } 327 if i < 0 { return 0 as *NxNspecRoute } 328 if i >= s.routes_count { return 0 as *NxNspecRoute } 329 return ((s.routes as i64) + i * NX_NSPEC_ROUTE_BYTES) as *NxNspecRoute 330} 331 332func nx_nspec_rate_bucket_at(s: *NxSiteDeploymentSpec, i: i64) -> *NxNspecRateBucket { 333 if s.valid != 1 { return 0 as *NxNspecRateBucket } 334 if i < 0 { return 0 as *NxNspecRateBucket } 335 if i >= s.rate_buckets_count { return 0 as *NxNspecRateBucket } 336 return ((s.rate_buckets as i64) + i * NX_NSPEC_RATE_BUCKET_BYTES) as *NxNspecRateBucket 337} 338 339// ===== Adders for sub-records (called by parser) ================================================= 340 341func nx_nspec_dns_add(s: *NxSiteDeploymentSpec, 342 kind: i64, 343 name_off: i64, name_len: i64, 344 value_off: i64, value_len: i64, 345 extra_off: i64, extra_len: i64) -> i64 { 346 if s.valid != 1 { return 0 - NX_NSPEC_BAD_INPUT } 347 if s.dns_records_count >= s.dns_records_cap { return 0 - NX_NSPEC_TOO_MANY_RECORDS } 348 if kind < 0 { return 0 - NX_NSPEC_BAD_ENUM } 349 if kind >= NX_NSPEC_DNS_N { return 0 - NX_NSPEC_BAD_ENUM } 350 let r: *NxNspecDnsRecord = ((s.dns_records as i64) + s.dns_records_count * NX_NSPEC_DNS_RECORD_BYTES) as *NxNspecDnsRecord 351 r.kind = kind 352 r.name_off = name_off; r.name_len = name_len 353 r.value_off = value_off; r.value_len = value_len 354 r.extra_off = extra_off; r.extra_len = extra_len 355 s.dns_records_count = s.dns_records_count + 1 356 return s.dns_records_count - 1 357} 358 359func nx_nspec_route_add(s: *NxSiteDeploymentSpec, 360 prefix_off: i64, prefix_len: i64, 361 handler: i64) -> i64 { 362 if s.valid != 1 { return 0 - NX_NSPEC_BAD_INPUT } 363 if s.routes_count >= s.routes_cap { return 0 - NX_NSPEC_TOO_MANY_ROUTES } 364 if handler < 0 { return 0 - NX_NSPEC_BAD_ENUM } 365 if handler >= NX_NSPEC_ROUTE_N { return 0 - NX_NSPEC_BAD_ENUM } 366 let r: *NxNspecRoute = ((s.routes as i64) + s.routes_count * NX_NSPEC_ROUTE_BYTES) as *NxNspecRoute 367 r.prefix_off = prefix_off; r.prefix_len = prefix_len 368 r.handler = handler 369 s.routes_count = s.routes_count + 1 370 return s.routes_count - 1 371} 372 373func nx_nspec_rate_add(s: *NxSiteDeploymentSpec, 374 prefix_off: i64, prefix_len: i64, 375 quota: i64, per_seconds: i64, scope: i64) -> i64 { 376 if s.valid != 1 { return 0 - NX_NSPEC_BAD_INPUT } 377 if s.rate_buckets_count >= s.rate_buckets_cap { return 0 - NX_NSPEC_TOO_MANY_BUCKETS } 378 if quota < 1 { return 0 - NX_NSPEC_BAD_INPUT } 379 if per_seconds < 1 { return 0 - NX_NSPEC_BAD_INPUT } 380 if scope < 0 { return 0 - NX_NSPEC_BAD_ENUM } 381 if scope > 1 { return 0 - NX_NSPEC_BAD_ENUM } 382 let r: *NxNspecRateBucket = ((s.rate_buckets as i64) + s.rate_buckets_count * NX_NSPEC_RATE_BUCKET_BYTES) as *NxNspecRateBucket 383 r.prefix_off = prefix_off; r.prefix_len = prefix_len 384 r.quota = quota 385 r.per_seconds = per_seconds 386 r.scope = scope 387 s.rate_buckets_count = s.rate_buckets_count + 1 388 return s.rate_buckets_count - 1 389} 390 391// ===== Accessors (frontend-friendly) ================================================= 392 393func nx_nspec_site_name(s: *NxSiteDeploymentSpec, out_ptr: *i64, out_n: *i64) -> i64 { 394 if s.valid != 1 { return 0 - NX_NSPEC_BAD_INPUT } 395 out_ptr[0] = (s.pool as i64) + s.site_name_off 396 out_n[0] = s.site_name_len 397 return NX_NSPEC_OK 398} 399 400func nx_nspec_dns_records_count(s: *NxSiteDeploymentSpec) -> i64 { 401 if s.valid != 1 { return 0 } 402 return s.dns_records_count 403} 404 405func nx_nspec_routes_count(s: *NxSiteDeploymentSpec) -> i64 { 406 if s.valid != 1 { return 0 } 407 return s.routes_count 408} 409 410func nx_nspec_rate_buckets_count(s: *NxSiteDeploymentSpec) -> i64 { 411 if s.valid != 1 { return 0 } 412 return s.rate_buckets_count 413}