betterwithage commited on
Commit
74887fd
·
verified ·
1 Parent(s): ada5a68

Wire FLEET (Vessels) + Voyage Risk; graceful consensus (amaru-independent); fix Living Organism load flash

Browse files

GAP-1: FLEET (Vessels) nav group (Fleet Overview, Maintenance & Compliance, Fleet Briefings) wired to platform seed-data/vessels/* served VERBATIM via killinchu_fleet_vessels.py under /api/killinchu/v1/fleet/* (18 vessels, 4 forecast modules, predictive-maintenance, certs, PSC deficiencies, AI briefings). Mandatory 'Sample fleet dataset - not a live AIS/class-society feed' label on every tab. GAP-2: Voyage Risk Exchange governed-decision card on Maritime tab (signals->forecast->evidence->recommendation w/ rollback->signed receipt; Trust score shown as advisory conjecture). QA fixes: (1) Consensus degrades GRACEFULLY - killinchu no longer hard-depends on amaru (non-essential, short timeout, quorum computed without amaru); (2) Living Organism loading state eliminates blank-canvas flash. Honesty doctrine preserved: Lambda=Conjecture 1, 5 proven formulas, SLSA L2, real cosign key keeps receipts PASS+tamper-FAIL, no jargon, no fabricated numbers.

fleet_vessels_data.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"vessels":[{"id":1,"name":"CONSTANTA SPIRIT","imo":"9732847","mmsi":"264700100","vesselType":"bulk","shipClass":"Handymax","flag":"Romania","yearBuilt":2019,"grossTonnage":33400,"deadweight":58000,"length":190,"beam":32.26,"draft":12.8,"status":"at_sea","fleetId":1,"currentLat":43.18,"currentLon":28.59,"currentSpeed":12.4,"currentHeading":195,"lastPort":"Constanta","nextPort":"Istanbul","eta":"2026-03-29","fuelConsumptionDaily":28,"co2EmissionsDaily":87.2,"ciiRating":"B","ciiValue":5.82,"attainedCII":5.82,"requiredCII":6.45,"hullCondition":91,"engineHealth":94,"maintenanceScore":90,"tce":18200,"utilization":92,"eexi":7.1,"tradeLane":"Black Sea — Marmara","operator":"Navrom SA","classificationSociety":"Bureau Veritas","builder":"Daewoo Shipbuilding"},{"id":2,"name":"MARE NIGRUM","imo":"9781234","mmsi":"264700200","vesselType":"tanker","shipClass":"Aframax","flag":"Romania","yearBuilt":2020,"grossTonnage":62500,"deadweight":115000,"length":250,"beam":44,"draft":15.2,"status":"at_sea","fleetId":1,"currentLat":41.02,"currentLon":29.01,"currentSpeed":10.8,"currentHeading":210,"lastPort":"Constanta","nextPort":"Augusta","eta":"2026-04-02","fuelConsumptionDaily":52,"co2EmissionsDaily":161.9,"ciiRating":"A","ciiValue":4.21,"attainedCII":4.21,"requiredCII":5.88,"hullCondition":95,"engineHealth":96,"maintenanceScore":94,"tce":28500,"utilization":96,"eexi":5.4,"tradeLane":"Black Sea — Mediterranean","operator":"Petrom Marine","classificationSociety":"Lloyd's Register","builder":"Samsung Heavy Industries"},{"id":3,"name":"COSCO SHANGHAI","imo":"9795432","mmsi":"413456780","vesselType":"container","shipClass":"Neo-Panamax","flag":"China","yearBuilt":2021,"grossTonnage":115000,"deadweight":136000,"length":366,"beam":51.2,"draft":16.0,"status":"at_sea","fleetId":2,"currentLat":34.05,"currentLon":-152.3,"currentSpeed":19.8,"currentHeading":275,"lastPort":"Ningbo-Zhoushan","nextPort":"Long Beach","eta":"2026-04-03","fuelConsumptionDaily":195,"co2EmissionsDaily":607.2,"ciiRating":"B","ciiValue":6.12,"attainedCII":6.12,"requiredCII":7.35,"hullCondition":96,"engineHealth":97,"maintenanceScore":95,"tce":31200,"utilization":97,"eexi":6.8,"tradeLane":"Trans-Pacific Eastbound","operator":"COSCO Shipping Lines","classificationSociety":"China Classification Society","builder":"Jiangsu New Yangzi Shipbuilding"},{"id":4,"name":"NORDIC WOLVERINE","imo":"9834567","mmsi":"258765000","vesselType":"tanker","shipClass":"VLCC","flag":"Norway","yearBuilt":2022,"grossTonnage":160000,"deadweight":300000,"length":333,"beam":60,"draft":22.5,"status":"at_sea","fleetId":4,"currentLat":26.2,"currentLon":56.3,"currentSpeed":14.1,"currentHeading":90,"lastPort":"Ras Tanura","nextPort":"Ningbo-Zhoushan","eta":"2026-04-15","fuelConsumptionDaily":85,"co2EmissionsDaily":264.7,"ciiRating":"B","ciiValue":2.18,"attainedCII":2.18,"requiredCII":2.95,"hullCondition":98,"engineHealth":99,"maintenanceScore":97,"tce":42100,"utilization":98,"eexi":4.2,"tradeLane":"AG — Far East","operator":"Frontline Ltd","classificationSociety":"DNV","builder":"Hyundai Heavy Industries"},{"id":5,"name":"OLDENDORFF BRISBANE","imo":"9756789","mmsi":"211345600","vesselType":"bulk","shipClass":"Capesize","flag":"Germany","yearBuilt":2018,"grossTonnage":92000,"deadweight":180000,"length":292,"beam":45,"draft":18.2,"status":"at_sea","fleetId":3,"currentLat":-33.9,"currentLon":18.4,"currentSpeed":13.2,"currentHeading":210,"lastPort":"Richards Bay","nextPort":"Qingdao","eta":"2026-04-18","fuelConsumptionDaily":48,"co2EmissionsDaily":149.5,"ciiRating":"C","ciiValue":4.52,"attainedCII":4.52,"requiredCII":4.1,"hullCondition":82,"engineHealth":85,"maintenanceScore":80,"tce":22800,"utilization":90,"eexi":9.1,"tradeLane":"South Africa — China Coal","operator":"Oldendorff Carriers","classificationSociety":"Germanischer Lloyd","builder":"Imabari Shipbuilding"},{"id":6,"name":"MAERSK EDINBURGH","imo":"9812345","mmsi":"219018600","vesselType":"container","shipClass":"Triple-E","flag":"Denmark","yearBuilt":2023,"grossTonnage":214000,"deadweight":198000,"length":400,"beam":58.6,"draft":16.5,"status":"at_sea","fleetId":2,"currentLat":1.3,"currentLon":103.8,"currentSpeed":16.5,"currentHeading":350,"lastPort":"Singapore","nextPort":"Rotterdam","eta":"2026-04-14","fuelConsumptionDaily":180,"co2EmissionsDaily":282.6,"ciiRating":"A","ciiValue":3.45,"attainedCII":3.45,"requiredCII":5.12,"hullCondition":99,"engineHealth":99,"maintenanceScore":98,"tce":38500,"utilization":99,"eexi":4.1,"tradeLane":"Asia — Europe (AE7)","operator":"Maersk Line","classificationSociety":"DNV","builder":"Daewoo Shipbuilding"},{"id":7,"name":"GALATA BRIDGE","imo":"9745678","mmsi":"271001234","vesselType":"bulk","shipClass":"Supramax","flag":"Turkey","yearBuilt":2017,"grossTonnage":32000,"deadweight":56000,"length":190,"beam":32.26,"draft":12.8,"status":"in_port","fleetId":1,"currentLat":44.17,"currentLon":28.65,"currentSpeed":0,"currentHeading":180,"lastPort":"Novorossiysk","nextPort":"Constanta","eta":"2026-03-28","fuelConsumptionDaily":0,"co2EmissionsDaily":0,"ciiRating":"B","ciiValue":5.55,"attainedCII":5.55,"requiredCII":6.45,"hullCondition":85,"engineHealth":88,"maintenanceScore":84,"tce":15600,"utilization":87,"eexi":7.8,"tradeLane":"Black Sea Intra-Regional","operator":"Arkas Holding","classificationSociety":"Turkish Lloyd","builder":"Oshima Shipbuilding"},{"id":8,"name":"EURONAV DANUBE","imo":"9823456","mmsi":"205456000","vesselType":"tanker","shipClass":"Suezmax","flag":"Belgium","yearBuilt":2021,"grossTonnage":82000,"deadweight":157000,"length":274,"beam":48,"draft":17.1,"status":"at_sea","fleetId":3,"currentLat":42.5,"currentLon":-38.7,"currentSpeed":14.2,"currentHeading":85,"lastPort":"Bonny Terminal","nextPort":"Rotterdam","eta":"2026-04-05","fuelConsumptionDaily":58,"co2EmissionsDaily":180.6,"ciiRating":"A","ciiValue":3.89,"attainedCII":3.89,"requiredCII":5.22,"hullCondition":97,"engineHealth":96,"maintenanceScore":95,"tce":34200,"utilization":96,"eexi":4.8,"tradeLane":"WAF — NW Europe","operator":"Euronav NV","classificationSociety":"Bureau Veritas","builder":"Samsung Heavy Industries"},{"id":9,"name":"PACIFIC BASIN TIANJIN","imo":"9767890","mmsi":"477234100","vesselType":"bulk","shipClass":"Ultramax","flag":"Hong Kong","yearBuilt":2020,"grossTonnage":38000,"deadweight":64000,"length":200,"beam":32.26,"draft":13.5,"status":"at_sea","fleetId":2,"currentLat":-5.8,"currentLon":106.8,"currentSpeed":11.8,"currentHeading":165,"lastPort":"Hay Point","nextPort":"Tanjung Priok","eta":"2026-03-30","fuelConsumptionDaily":30,"co2EmissionsDaily":93.4,"ciiRating":"B","ciiValue":5.12,"attainedCII":5.12,"requiredCII":6.02,"hullCondition":90,"engineHealth":92,"maintenanceScore":88,"tce":16800,"utilization":91,"eexi":6.9,"tradeLane":"Australia — SE Asia","operator":"Pacific Basin Shipping","classificationSociety":"ClassNK","builder":"Tsuneishi Shipbuilding"},{"id":10,"name":"TORM HELLAS","imo":"9698765","mmsi":"219019800","vesselType":"tanker","shipClass":"MR2 Product Tanker","flag":"Denmark","yearBuilt":2014,"grossTonnage":30000,"deadweight":49000,"length":183,"beam":32.2,"draft":13.1,"status":"maintenance","fleetId":1,"currentLat":44.43,"currentLon":28.66,"currentSpeed":0,"currentHeading":0,"lastPort":"Constanta","nextPort":"Constanta","eta":"2026-04-15","fuelConsumptionDaily":0,"co2EmissionsDaily":0,"ciiRating":"D","ciiValue":9.82,"attainedCII":9.82,"requiredCII":6.88,"hullCondition":62,"engineHealth":58,"maintenanceScore":55,"tce":0,"utilization":45,"eexi":14.2,"tradeLane":"Drydock — Constanta Shipyard","operator":"TORM A/S","classificationSociety":"Lloyd's Register","builder":"Guangzhou Shipyard"},{"id":11,"name":"MSC AURORA","imo":"9845678","mmsi":"353456700","vesselType":"container","shipClass":"Megamax-24","flag":"Panama","yearBuilt":2024,"grossTonnage":228000,"deadweight":220000,"length":400,"beam":61.5,"draft":17.4,"status":"at_sea","fleetId":2,"currentLat":22.3,"currentLon":114.2,"currentSpeed":21.2,"currentHeading":315,"lastPort":"Yantian","nextPort":"Tanjung Pelepas","eta":"2026-03-31","fuelConsumptionDaily":225,"co2EmissionsDaily":700.6,"ciiRating":"A","ciiValue":3.12,"attainedCII":3.12,"requiredCII":4.85,"hullCondition":99,"engineHealth":99,"maintenanceScore":99,"tce":35800,"utilization":98,"eexi":3.9,"tradeLane":"Far East — Mediterranean","operator":"MSC Mediterranean Shipping","classificationSociety":"RINA","builder":"Hyundai Samho Heavy Industries"},{"id":12,"name":"DANUBIA STAR","imo":"9712345","mmsi":"264700300","vesselType":"cargo","shipClass":"River-Sea Type","flag":"Romania","yearBuilt":2016,"grossTonnage":5800,"deadweight":7200,"length":140,"beam":16.8,"draft":4.5,"status":"at_sea","fleetId":1,"currentLat":45.15,"currentLon":29.68,"currentSpeed":8.2,"currentHeading":120,"lastPort":"Galati","nextPort":"Constanta","eta":"2026-03-28","fuelConsumptionDaily":8,"co2EmissionsDaily":24.9,"ciiRating":"B","ciiValue":5.65,"attainedCII":5.65,"requiredCII":6.88,"hullCondition":78,"engineHealth":80,"maintenanceScore":76,"tce":8500,"utilization":82,"eexi":8.4,"tradeLane":"Danube River — Black Sea","operator":"Navrom SA","classificationSociety":"Romanian Naval Authority","builder":"Santierul Naval Galati"},{"id":13,"name":"STENA POLARIS","imo":"9856789","mmsi":"265789000","vesselType":"tanker","shipClass":"IMOIIMAX","flag":"Sweden","yearBuilt":2022,"grossTonnage":25000,"deadweight":37000,"length":176,"beam":32.2,"draft":12.5,"status":"at_sea","fleetId":1,"currentLat":40.65,"currentLon":27.95,"currentSpeed":11.5,"currentHeading":45,"lastPort":"Tüpraş İzmit","nextPort":"Constanta","eta":"2026-03-29","fuelConsumptionDaily":22,"co2EmissionsDaily":68.5,"ciiRating":"A","ciiValue":4.15,"attainedCII":4.15,"requiredCII":5.95,"hullCondition":97,"engineHealth":96,"maintenanceScore":95,"tce":19800,"utilization":93,"eexi":5.2,"tradeLane":"Marmara — Black Sea Products","operator":"Stena Bulk AB","classificationSociety":"DNV","builder":"Guangzhou Shipyard International"},{"id":14,"name":"CMA CGM BOSPHORUS","imo":"9867890","mmsi":"228345600","vesselType":"container","shipClass":"Panamax","flag":"France","yearBuilt":2018,"grossTonnage":52000,"deadweight":63000,"length":265,"beam":32.2,"draft":13.5,"status":"anchored","fleetId":1,"currentLat":41.18,"currentLon":29.08,"currentSpeed":0,"currentHeading":90,"lastPort":"Piraeus","nextPort":"Constanta","eta":"2026-03-29","fuelConsumptionDaily":0,"co2EmissionsDaily":0,"ciiRating":"B","ciiValue":5.88,"attainedCII":5.88,"requiredCII":6.95,"hullCondition":89,"engineHealth":91,"maintenanceScore":88,"tce":21500,"utilization":88,"eexi":7.2,"tradeLane":"Med — Black Sea Feeder","operator":"CMA CGM Group","classificationSociety":"Bureau Veritas","builder":"Hanjin Heavy Industries"},{"id":15,"name":"VALE BRASIL","imo":"9878901","mmsi":"710567800","vesselType":"bulk","shipClass":"Valemax (VLOC)","flag":"Brazil","yearBuilt":2023,"grossTonnage":200000,"deadweight":400000,"length":362,"beam":65,"draft":23.0,"status":"at_sea","fleetId":3,"currentLat":-2.5,"currentLon":-44.3,"currentSpeed":13.5,"currentHeading":30,"lastPort":"Ponta da Madeira","nextPort":"Qingdao","eta":"2026-04-22","fuelConsumptionDaily":75,"co2EmissionsDaily":233.6,"ciiRating":"A","ciiValue":1.85,"attainedCII":1.85,"requiredCII":2.62,"hullCondition":99,"engineHealth":98,"maintenanceScore":97,"tce":28900,"utilization":95,"eexi":3.2,"tradeLane":"Brazil — China Iron Ore","operator":"Vale SA","classificationSociety":"DNV","builder":"CSSC Dalian Shipbuilding"},{"id":16,"name":"ARCTIC AURORA","imo":"9889012","mmsi":"258900100","vesselType":"tanker","shipClass":"Arc7 LNG Carrier","flag":"Norway","yearBuilt":2024,"grossTonnage":129000,"deadweight":95000,"length":299,"beam":50,"draft":12.0,"status":"at_sea","fleetId":4,"currentLat":68.95,"currentLon":33.05,"currentSpeed":16.8,"currentHeading":270,"lastPort":"Sabetta","nextPort":"Zeebrugge","eta":"2026-04-06","fuelConsumptionDaily":120,"co2EmissionsDaily":280.8,"ciiRating":"A","ciiValue":3.55,"attainedCII":3.55,"requiredCII":4.82,"hullCondition":99,"engineHealth":99,"maintenanceScore":98,"tce":52000,"utilization":97,"eexi":3.8,"tradeLane":"NSR — Yamal LNG","operator":"MOL/Teekay LNG","classificationSociety":"DNV","builder":"Daewoo Shipbuilding"},{"id":17,"name":"EVERGREEN TRIUMPH","imo":"9890123","mmsi":"416234500","vesselType":"container","shipClass":"Neo-Panamax","flag":"Taiwan","yearBuilt":2022,"grossTonnage":120000,"deadweight":140000,"length":334,"beam":51.2,"draft":16.0,"status":"at_sea","fleetId":2,"currentLat":15.4,"currentLon":120.6,"currentSpeed":18.5,"currentHeading":180,"lastPort":"Kaohsiung","nextPort":"Port Klang","eta":"2026-04-03","fuelConsumptionDaily":175,"co2EmissionsDaily":545.0,"ciiRating":"B","ciiValue":5.95,"attainedCII":5.95,"requiredCII":7.12,"hullCondition":97,"engineHealth":96,"maintenanceScore":95,"tce":27400,"utilization":95,"eexi":6.5,"tradeLane":"Intra-Asia","operator":"Evergreen Marine Corp","classificationSociety":"ClassNK","builder":"Samsung Heavy Industries"},{"id":18,"name":"MINERVA SOPHIA","imo":"9801234","mmsi":"240567800","vesselType":"tanker","shipClass":"Suezmax","flag":"Greece","yearBuilt":2019,"grossTonnage":82000,"deadweight":158000,"length":274,"beam":48,"draft":17.1,"status":"at_sea","fleetId":1,"currentLat":37.0,"currentLon":26.5,"currentSpeed":13.4,"currentHeading":30,"lastPort":"Ceyhan","nextPort":"Trieste","eta":"2026-04-01","fuelConsumptionDaily":55,"co2EmissionsDaily":171.3,"ciiRating":"B","ciiValue":5.42,"attainedCII":5.42,"requiredCII":5.88,"hullCondition":88,"engineHealth":90,"maintenanceScore":86,"tce":31800,"utilization":93,"eexi":6.2,"tradeLane":"Black Sea — Med Crude","operator":"Minerva Marine Inc","classificationSociety":"Lloyd's Register","builder":"Hyundai Heavy Industries"}],"forecast-modules":[{"id":1,"title":"Fleet Average TCE","metric":"$/day","currentValue":24850,"forecastValue":27200,"forecastDate":"2026-06-30","confidence":78,"trend":"up","dataPoints":[{"date":"2025-10","value":21800},{"date":"2025-11","value":22400},{"date":"2025-12","value":23100},{"date":"2026-01","value":23600},{"date":"2026-02","value":24200},{"date":"2026-03","value":24850},{"date":"2026-04","value":25500,"forecast":25500},{"date":"2026-05","value":26400,"forecast":26400},{"date":"2026-06","value":27200,"forecast":27200}]},{"id":2,"title":"Fleet CII (gCO2/DWT·nm)","metric":"gCO2/DWT·nm","currentValue":4.85,"forecastValue":4.42,"forecastDate":"2026-06-30","confidence":72,"trend":"down","dataPoints":[{"date":"2025-10","value":5.62},{"date":"2025-11","value":5.41},{"date":"2025-12","value":5.28},{"date":"2026-01","value":5.12},{"date":"2026-02","value":4.98},{"date":"2026-03","value":4.85},{"date":"2026-04","value":4.71,"forecast":4.71},{"date":"2026-05","value":4.55,"forecast":4.55},{"date":"2026-06","value":4.42,"forecast":4.42}]},{"id":3,"title":"Fleet Utilization Rate","metric":"%","currentValue":91.8,"forecastValue":93.5,"forecastDate":"2026-06-30","confidence":85,"trend":"up","dataPoints":[{"date":"2025-10","value":88.5},{"date":"2025-11","value":89.2},{"date":"2025-12","value":89.8},{"date":"2026-01","value":90.4},{"date":"2026-02","value":91.1},{"date":"2026-03","value":91.8},{"date":"2026-04","value":92.2,"forecast":92.2},{"date":"2026-05","value":92.8,"forecast":92.8},{"date":"2026-06","value":93.5,"forecast":93.5}]},{"id":4,"title":"Baltic Dry Index Correlation","metric":"BDI Points","currentValue":1842,"forecastValue":2150,"forecastDate":"2026-06-30","confidence":68,"trend":"up","dataPoints":[{"date":"2025-10","value":1520},{"date":"2025-11","value":1580},{"date":"2025-12","value":1650},{"date":"2026-01","value":1720},{"date":"2026-02","value":1780},{"date":"2026-03","value":1842},{"date":"2026-04","value":1920,"forecast":1920},{"date":"2026-05","value":2040,"forecast":2040},{"date":"2026-06","value":2150,"forecast":2150}]}],"predictive-maintenance":[{"id":1,"vesselId":9,"vesselName":"PACIFIC BASIN TIANJIN","component":"Propeller Shaft Stern Tube Bearing","failureProbability":73,"predictedFailureDate":"2026-05-10","recommendedAction":"Schedule emergency docking for bearing replacement at PT United Shipyard Tanjung Priok","estimatedCost":120000,"confidence":73,"riskLevel":"High"},{"id":2,"vesselId":10,"vesselName":"TORM HELLAS","component":"Main Engine Turbocharger (MAN TCA66)","failureProbability":62,"predictedFailureDate":"2026-06-15","recommendedAction":"Replace turbocharger cartridge during current Constanta Shipyard drydock","estimatedCost":85000,"confidence":68,"riskLevel":"High"},{"id":3,"vesselId":18,"vesselName":"MINERVA SOPHIA","component":"Cargo Pump Mechanical Seal (Framo SD200)","failureProbability":55,"predictedFailureDate":"2026-07-20","recommendedAction":"Order spare seals and plan replacement during next discharge at Trieste","estimatedCost":12000,"confidence":71,"riskLevel":"Medium"},{"id":4,"vesselId":7,"vesselName":"GALATA BRIDGE","component":"Ballast Water UV Reactor (Alfa Laval PureBallast)","failureProbability":48,"predictedFailureDate":"2026-08-01","recommendedAction":"Replace UV lamp assembly during scheduled BWTS maintenance at Constanta","estimatedCost":35000,"confidence":65,"riskLevel":"Medium"},{"id":5,"vesselId":5,"vesselName":"OLDENDORFF BRISBANE","component":"Auxiliary Boiler Tubes (Aalborg AQ-12)","failureProbability":41,"predictedFailureDate":"2026-09-10","recommendedAction":"Conduct ultrasonic thickness measurement during next port stay at Qingdao","estimatedCost":55000,"confidence":60,"riskLevel":"Medium"},{"id":6,"vesselId":1,"vesselName":"CONSTANTA SPIRIT","component":"Steering Gear Hydraulic Pump (Rolls-Royce)","failureProbability":28,"predictedFailureDate":"2026-11-15","recommendedAction":"Replace hydraulic pump seal kit during next scheduled Constanta port call","estimatedCost":8000,"confidence":58,"riskLevel":"Low"}],"compliance-certificates":[{"id":1,"vesselId":10,"vesselName":"TORM HELLAS","certificateType":"Safety Management Certificate (SMC)","issuer":"Lloyd's Register","issuedDate":"2022-06-15","expiryDate":"2026-04-10","status":"Expiring Soon","daysUntilExpiry":15,"regulation":"ISM Code Ch. 13"},{"id":2,"vesselId":4,"vesselName":"NORDIC WOLVERINE","certificateType":"International Oil Pollution Prevention (IOPP)","issuer":"DNV","issuedDate":"2022-09-01","expiryDate":"2027-09-01","status":"Valid","daysUntilExpiry":524,"regulation":"MARPOL Annex I, Reg. 7"},{"id":3,"vesselId":5,"vesselName":"OLDENDORFF BRISBANE","certificateType":"ISM Document of Compliance (DOC)","issuer":"Germanischer Lloyd","issuedDate":"2023-01-15","expiryDate":"2026-05-15","status":"Expiring Soon","daysUntilExpiry":50,"regulation":"ISM Code Ch. 13.2"},{"id":4,"vesselId":3,"vesselName":"COSCO SHANGHAI","certificateType":"SOLAS Safety Equipment Certificate (SEC)","issuer":"China Classification Society","issuedDate":"2023-03-20","expiryDate":"2028-03-20","status":"Valid","daysUntilExpiry":724,"regulation":"SOLAS Ch. I/Reg. 12"},{"id":5,"vesselId":6,"vesselName":"MAERSK EDINBURGH","certificateType":"Energy Efficiency Existing Ship Index (EEXI)","issuer":"DNV","issuedDate":"2023-01-01","expiryDate":"2028-01-01","status":"Valid","daysUntilExpiry":645,"regulation":"MARPOL Annex VI Reg. 23"},{"id":6,"vesselId":18,"vesselName":"MINERVA SOPHIA","certificateType":"International Air Pollution Prevention (IAPP)","issuer":"Lloyd's Register","issuedDate":"2021-03-30","expiryDate":"2026-03-30","status":"Expired","daysUntilExpiry":-4,"regulation":"MARPOL Annex VI Reg. 6"},{"id":7,"vesselId":9,"vesselName":"PACIFIC BASIN TIANJIN","certificateType":"Ballast Water Management (BWM)","issuer":"ClassNK","issuedDate":"2022-08-15","expiryDate":"2027-08-15","status":"Valid","daysUntilExpiry":507,"regulation":"BWM Convention Art. 9"},{"id":8,"vesselId":16,"vesselName":"ARCTIC AURORA","certificateType":"Polar Ship Certificate (PSC)","issuer":"DNV","issuedDate":"2024-01-10","expiryDate":"2029-01-10","status":"Valid","daysUntilExpiry":1021,"regulation":"SOLAS Ch. XIV / Polar Code"},{"id":9,"vesselId":8,"vesselName":"EURONAV DANUBE","certificateType":"International Sewage Pollution Prevention (ISPP)","issuer":"Bureau Veritas","issuedDate":"2023-05-10","expiryDate":"2028-05-10","status":"Valid","daysUntilExpiry":775,"regulation":"MARPOL Annex IV Reg. 5"},{"id":10,"vesselId":11,"vesselName":"MSC AURORA","certificateType":"Energy Efficiency Management Plan (SEEMP Part III)","issuer":"RINA","issuedDate":"2024-03-01","expiryDate":"2029-03-01","status":"Valid","daysUntilExpiry":1070,"regulation":"MARPOL Annex VI Reg. 26"}],"port-state-deficiencies":[{"id":1,"vesselId":10,"vesselName":"TORM HELLAS","port":"Constanta","inspectionDate":"2026-03-01","deficiencyCode":"01302","description":"Fire detection system — smoke detector in engine room No. 2 zone inoperative. Non-compliance with SOLAS Ch. II-2/Reg. 7.2.2.","severity":"High","status":"Open","rectifiedDate":null,"mouRegime":"Paris MOU"},{"id":2,"vesselId":4,"vesselName":"NORDIC WOLVERINE","port":"Fujairah","inspectionDate":"2026-02-15","deficiencyCode":"07410","description":"MARPOL Annex I — oil water separator discharge exceeding 15 ppm limit. Sample analysis showed 22 ppm. OWS alarm not functioning.","severity":"High","status":"Rectified","rectifiedDate":"2026-02-20","mouRegime":"Riyadh MOU"},{"id":3,"vesselId":5,"vesselName":"OLDENDORFF BRISBANE","port":"Qingdao","inspectionDate":"2026-03-10","deficiencyCode":"06110","description":"Lifesaving appliances — lifeboat on-load release mechanism requires servicing per MSC.1/Circ.1206/Rev.2. Annual maintenance record incomplete.","severity":"Medium","status":"Open","rectifiedDate":null,"mouRegime":"Tokyo MOU"},{"id":4,"vesselId":18,"vesselName":"MINERVA SOPHIA","port":"Trieste","inspectionDate":"2026-01-20","deficiencyCode":"09510","description":"MLC 2006 — crew rest hour records incomplete for Chief Officer and 2nd Engineer. Work/rest analysis shows potential non-compliance with Standard A2.3.","severity":"Low","status":"Rectified","rectifiedDate":"2026-01-25","mouRegime":"Paris MOU"},{"id":5,"vesselId":9,"vesselName":"PACIFIC BASIN TIANJIN","port":"Tanjung Priok","inspectionDate":"2026-03-05","deficiencyCode":"04315","description":"Navigation equipment — ECDIS backup arrangement not compliant with SOLAS Ch. V/Reg. 19.2.1.4. Backup ECDIS showing chart datum error.","severity":"Medium","status":"Open","rectifiedDate":null,"mouRegime":"Tokyo MOU"},{"id":6,"vesselId":12,"vesselName":"DANUBIA STAR","port":"Constanta","inspectionDate":"2026-02-28","deficiencyCode":"01520","description":"Structural safety — wastage found on main deck plating exceeding 20% of original thickness. Class condition noted by Romanian Naval Authority.","severity":"High","status":"Open","rectifiedDate":null,"mouRegime":"Paris MOU"}],"ai-briefings":[{"id":1,"title":"Black Sea Fleet CII Performance Analysis","category":"Performance","severity":"Warning","summary":"Black Sea Division fleet-wide CII trending 8% above required thresholds. TORM HELLAS rated D and OLDENDORFF BRISBANE at C boundary — both at risk of operational restrictions under IMO MEPC.352(78) 2026 tightening.","details":"9-month emission data analysis per MARPOL Annex VI Regulation 28 shows attained CII deterioration across 4 vessels. Primary contributors: TORM HELLAS (attained 9.82 vs required 6.88) and OLDENDORFF BRISBANE (4.52 vs 4.10). Hull fouling penalty estimated at 12-15% fuel increase. Constanta Shipyard drydock capacity available Q2 2026. Wind-assisted propulsion (Norsepower Rotor Sail) retrofit estimated ROI: 18 months.","confidence":91,"generatedAt":"2026-03-26T14:00:00Z","actionItems":["Submit CII Corrective Action Plan to flag state per MEPC.355(78) by June 2026","Schedule hull cleaning for TORM HELLAS at Constanta Shipyard during current drydock","Implement JIT arrival optimization for Bosporus transit to reduce idle time emissions","Evaluate Norsepower Rotor Sail retrofit for OLDENDORFF BRISBANE (est. 8% fuel saving)"],"affectedVessels":["TORM HELLAS","OLDENDORFF BRISBANE","MINERVA SOPHIA"]},{"id":2,"title":"Bosporus Strait Congestion — Route Optimization Required","category":"Operations","severity":"Warning","summary":"Turkish Straits VTS reporting 72-hour northbound transit delays. 23 vessels queued at southern anchorage. CMA CGM BOSPHORUS anchored since March 26. Revenue loss estimated at $45,000/day for detained fleet.","details":"Kıyı Emniyeti (Turkish Coastal Safety) data shows abnormal congestion due to combined factors: (1) Russian grain export surge, (2) seasonal LPG traffic increase from Ceyhan, (3) maintenance at İstanbul Strait radar station. Historical analysis suggests congestion will persist 5-7 days. Alternative routing via Suez Canal adds 4 days but avoids $180K demurrage exposure. JIT arrival coordination with Constanta Port Authority recommended.","confidence":87,"generatedAt":"2026-03-26T13:00:00Z","actionItems":["Coordinate JIT arrival with Constanta Port Authority (APMC) for STENA POLARIS","Request priority transit slot for CMA CGM BOSPHORUS via agent (Marline Agency Istanbul)","Evaluate Suez Canal alternative for non-time-critical Black Sea cargoes","Brief commercial team on potential 5-7 day delay impact on Q1 performance"],"affectedVessels":["CMA CGM BOSPHORUS","STENA POLARIS","CONSTANTA SPIRIT"]},{"id":3,"title":"EU ETS Maritime Compliance — Q1 Allowance Deadline","category":"Compliance","severity":"Critical","summary":"EU ETS maritime phase-in requires 40% of 2026 emissions covered by allowances. Submission deadline April 30. 8 vessels in fleet have EU port calls requiring reporting under Regulation 2023/959.","details":"Under EU ETS Directive 2003/87/EC as amended by 2023/959, maritime emissions for voyages to/from EU ports must be covered. Fleet exposure: estimated 42,000 EU Allowances (EUAs) at current price €85/EUA = €3.57M obligation. Vessels affected: MAERSK EDINBURGH, EURONAV DANUBE, MINERVA SOPHIA, CMA CGM BOSPHORUS, MSC AURORA, TORM HELLAS, CONSTANTA SPIRIT, DANUBIA STAR. MRV data from Thetis-MRV must reconcile with ETS reporting.","confidence":95,"generatedAt":"2026-03-26T12:00:00Z","actionItems":["Verify EU MRV emissions data in Thetis-MRV for all 8 vessels with EU port calls","Purchase 42,000 EUAs before April 30 deadline at current spot price","Implement FuelEU Maritime preparedness for 2025 well-to-wake intensity targets","Update voyage P&L models to include EU ETS cost per voyage for commercial team"],"affectedVessels":["MAERSK EDINBURGH","EURONAV DANUBE","MINERVA SOPHIA","CMA CGM BOSPHORUS","MSC AURORA","TORM HELLAS","CONSTANTA SPIRIT","DANUBIA STAR"]},{"id":4,"title":"Predictive Maintenance Alert — PACIFIC BASIN TIANJIN Stern Tube","category":"Maintenance","severity":"Warning","summary":"ML vibration analysis predicts 73% probability of stern tube bearing failure within 45 days. ClassNK Rule Part C 5.2 requires docking survey. Tanjung Priok offers lowest-cost repair window.","details":"SKF Copperhead vibration monitoring trend: propeller shaft 2x RPM harmonic increasing at 0.3 mm/s per week (current: 4.2 mm/s, ClassNK limit: 4.5 mm/s). Pattern matches pre-failure signature from fleet database (87% match to PACIFIC BASIN WUHAN failure 2024). Stern tube oil consumption: 1.2L/day (normal: 0.3L/day). PT United Shipyard Tanjung Priok quoted $95K for emergency docking incl. bearing replacement. Lead time for Wartsila lip seal assembly: 14 days.","confidence":73,"generatedAt":"2026-03-26T11:00:00Z","actionItems":["Schedule emergency docking at PT United Shipyard Tanjung Priok (earliest slot: April 8)","Order replacement Wartsila lip seal assembly — DDP Jakarta (14-day lead)","Increase vibration monitoring to 4-hour intervals","Prepare ClassNK surveyor request for tail shaft survey per Rule Part C 5.2"],"affectedVessels":["PACIFIC BASIN TIANJIN"]}]}
killinchu_elite_console.py CHANGED
@@ -391,6 +391,10 @@ details.raw{margin-top:1rem;} details.raw summary{cursor:pointer;font-family:var
391
  .verify-badge .dot{width:10px;height:10px;border-radius:50%;background:currentColor;box-shadow:0 0 8px currentColor;}
392
  /* ===== GENIUS VISUALS (inherited from a11oy command platform) ===== */
393
  .graph3d.hero{height:520px;}
 
 
 
 
394
  .echart{height:360px;width:100%;}
395
  .echart.tall{height:480px;}
396
  .globe3d{height:520px;width:100%;border-radius:9px;overflow:hidden;border:1px solid var(--gold-line);background:#060606;}
@@ -438,6 +442,11 @@ details.raw{margin-top:1rem;} details.raw summary{cursor:pointer;font-family:var
438
  <div class="nav-item" data-view="maritime" onclick="go('maritime')"><span class="ico">⚓</span>Maritime Picture</div>
439
  <div class="nav-item" data-view="sanctions" onclick="go('sanctions')"><span class="ico">◴</span>Sanctions &amp; Dark-Vessel</div>
440
 
 
 
 
 
 
441
  <div class="nav-group">Decide &amp; Govern</div>
442
  <div class="nav-item" data-view="roe" onclick="go('roe')"><span class="ico">⊞</span>Engagement Rules</div>
443
  <div class="nav-item" data-view="lambda" onclick="go('lambda')"><span class="ico">◈</span>Trust Score Monitor</div>
@@ -481,6 +490,7 @@ details.raw{margin-top:1rem;} details.raw summary{cursor:pointer;font-family:var
481
  <script>
482
  const BASE = 'https://szlholdings-killinchu.hf.space';
483
  const API = BASE + '/api/killinchu/v1';
 
484
 
485
  // Keep ONE persistent WebGL context alive for the whole session before any 3D view
486
  // initialises. On software-GL (SwiftShader) hosts the GL-context pool is tiny and
@@ -531,6 +541,14 @@ function barH(id,labels,data,colors){mkChart(id,{type:'bar',data:{labels,dataset
531
  function barV(id,labels,data,colors){mkChart(id,{type:'bar',data:{labels,datasets:[{data,backgroundColor:colors||TEAL,borderRadius:4}]},options:{scales:{x:{grid:{display:false},ticks:{color:'#9a9a9a',font:{size:9}}},y:{grid:{color:GRID},ticks:{color:DIM}}},plugins:{legend:{display:false}},responsive:true,maintainAspectRatio:false}});}
532
  function lineSpark(id,labels,data,color){mkChart(id,{type:'line',data:{labels,datasets:[{data,borderColor:color||TEAL,backgroundColor:'rgba(95,179,163,0.12)',fill:true,tension:.35,pointRadius:0,borderWidth:1.6}]},options:{scales:{x:{display:false},y:{display:false}},plugins:{legend:{display:false},tooltip:{enabled:false}},responsive:true,maintainAspectRatio:false}});}
533
  function doughnut(id,labels,data,colors){mkChart(id,{type:'doughnut',data:{labels,datasets:[{data,backgroundColor:colors,borderColor:'#0a0a0a',borderWidth:2}]},options:{cutout:'62%',plugins:{legend:{display:false},tooltip:{enabled:true}},responsive:true,maintainAspectRatio:false}});}
 
 
 
 
 
 
 
 
534
  let _fg=null;
535
  function mesh3d(id,nodes,links,_try){const host=el(id);if(!host||!window.ForceGraph3D)return;host.innerHTML='';try{_fg=ForceGraph3D()(host).backgroundColor('rgba(0,0,0,0)').width(host.clientWidth).height(host.clientHeight).graphData({nodes,links}).nodeLabel('name').nodeColor(n=>n.color||TEAL).nodeVal(n=>n.val||4).linkColor(()=>'rgba(201,183,135,0.45)').linkWidth(1.2).linkDirectionalParticles(2).linkDirectionalParticleSpeed(0.006).linkDirectionalParticleColor(()=>TEAL).showNavInfo(false);setTimeout(()=>{try{_fg.width(host.clientWidth).height(host.clientHeight);}catch(e){}
536
  /* GL pool can be momentarily exhausted on software-GL; if no WebGL canvas appeared, re-init once. */
@@ -758,6 +776,43 @@ async function vessel_alert_verify(tamper){
758
  }catch(e){ setVAlertBadge('fail','ERROR'); if(el('valert-detail')) el('valert-detail').textContent='retry: '+e.message; }
759
  }
760
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
761
 
762
  // ===================== VIEWS =====================
763
  const VIEWS = {
@@ -868,6 +923,10 @@ const VIEWS = {
868
  <div class="btns"><button class="btn teal" onclick="vessel_owner()">▶ Trace ownership</button></div>
869
  <div id="own-host"><div class="graph3d" id="owner-3d" style="height:320px"></div></div>
870
  <div class="legend"><span>Ownership chain: registered operator → shell companies → ultimate owner. Sample graph for the demo data set.</span></div></div>
 
 
 
 
871
  <details class="raw"><summary>raw AIS replay sample set</summary><pre class="out" id="maritime-raw">—</pre></details>
872
  <div class="honesty"><b>Honest by design.</b> This maritime picture is an <b>AIS replay — a sample track set, not a live feed</b> (no AIS provider is wired). Sanctions and ownership are screened against a small <b>sample list</b> in OFAC/UN/EU format, not full real-time coverage. Vessel-alert receipts are <b>genuinely signed</b> with killinchu's real key and verifiable offline — the same signed-receipt thesis as the drone side.</div>`;
873
  setOut('maritime-raw', V);
@@ -919,6 +978,160 @@ const VIEWS = {
919
  el('sanc-pick').addEventListener('change',()=>{const v=V.find(x=>x.id===el('sanc-pick').value); if(v) sanctions_drawGap(v);});
920
  }},
921
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
922
  // ── 3.4 ROE Policy Editor ────────────────────────────────────────
923
  roe:{title:'Engagement Rules',badge:'LIVE POLICY',sub:'The rules that decide whether a drone may be engaged — speed limits, required ID, allowed directions. Test a track against the current rules; every check produces a genuinely signed receipt.',
924
  render:async(c)=>{
@@ -1052,23 +1265,49 @@ const VIEWS = {
1052
  <div class="btns"><button class="btn teal" onclick="bft_exec()">▶ Execute demo mission</button></div>
1053
  <div id="bft-exec-summary" class="mono dim" style="font-size:12px;margin-bottom:.5rem">— click to execute —</div>
1054
  <details class="raw"><summary>raw /uds/v1/mission/execute</summary><pre class="out" id="bft-exec-out">—</pre></details></div>
 
1055
  <details class="raw"><summary>raw /uds/v1/healthz</summary><pre class="out" id="bft-raw">—</pre></details>
1056
  ${HONEST}`;
1057
  try{
1058
  const d = await getJSON(BASE+'/api/killinchu/uds/v1/healthz');
1059
  setOut('bft-raw',d);
1060
- el('k-quorum').textContent = d.quorum_possible ? 'CONSENSUS POSSIBLE' : 'DEGRADED';
1061
- el('k-quorum').className = 'v '+(d.quorum_possible?'live':'warn');
1062
  const organs=Object.entries(d.organs||{});
1063
  let online=0; organs.forEach(([,o])=>{if(o.status==='ok')online++;});
 
 
 
 
 
 
 
 
 
 
 
1064
  if(el('bft-count')) el('bft-count').textContent=online;
1065
- doughnut('bft-donut',['online','unreachable'],[online,Math.max(0,organs.length-online)],[TEAL,RED]);
 
 
 
 
 
 
 
 
 
 
 
1066
  const h = el('bft-organs'); h.innerHTML='';
1067
  organs.forEach(([name,o])=>{
1068
  const ok = o.status==='ok';
 
 
 
 
1069
  h.insertAdjacentHTML('beforeend',`<div class="row">
1070
- <span class="badge ${ok?'b-live':'b-warn'}">${ok?'ONLINE':esc(String(o.status||'').toUpperCase()||'DOWN')}</span>
1071
- <span>${esc(name)}</span>
1072
  <span class="spacer mono dim">${o.local?'local':''} ${o.http?'HTTP '+o.http:''} ${o.latency_ms?Math.round(o.latency_ms)+'ms':''}</span>
1073
  </div>`);
1074
  });
@@ -1328,7 +1567,7 @@ cosign verify-blob --key cosign.pub --signature sig.b64 payload.bin</pre></div>
1328
  <div class="kpi"><div class="k">Organs reachable</div><div class="v live" id="org-reach">probing…</div></div>
1329
  <div class="kpi"><div class="k">Links to brain</div><div class="v teal" id="org-links">—</div></div>
1330
  <div class="kpi"><div class="k">Signed spans</div><div class="v" id="org-spans">—</div><div class="d">live audit depth</div></div></div>
1331
- <div class="card"><div class="card-h"><span class="card-t">The brain at the center — live 3D</span><span class="card-ep">drag to orbit · particles = live links</span></div><div class="graph3d hero" id="org-3d"></div><div class="brain-note">a11oy is the orchestrator brain: every edge terminates at the gold core. killinchu is the field/Navy surface sharing that brain. Health from the live observability probe.</div></div>
1332
  <div class="card"><div class="card-h"><span class="card-t">Organ health detail</span><span class="card-ep">live probe</span></div><div id="org-host"><div class="row mono dim">probing…</div></div>
1333
  <details class="raw"><summary>raw /observability/summary</summary><pre class="out" id="org-raw">loading…</pre></details></div>${HONEST}`;window.organism_load();}},
1334
 
 
391
  .verify-badge .dot{width:10px;height:10px;border-radius:50%;background:currentColor;box-shadow:0 0 8px currentColor;}
392
  /* ===== GENIUS VISUALS (inherited from a11oy command platform) ===== */
393
  .graph3d.hero{height:520px;}
394
+ .org-loading{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;pointer-events:none;}
395
+ .graph3d{position:relative;}
396
+ .org-pulse{width:54px;height:54px;border-radius:50%;background:radial-gradient(circle,var(--gold,#c9b787) 0%,rgba(201,183,135,0.15) 60%,transparent 72%);box-shadow:0 0 0 0 rgba(201,183,135,0.45);animation:orgPulse 1.6s ease-out infinite;}
397
+ @keyframes orgPulse{0%{transform:scale(.85);box-shadow:0 0 0 0 rgba(201,183,135,0.45);}70%{transform:scale(1.05);box-shadow:0 0 0 22px rgba(201,183,135,0);}100%{transform:scale(.85);box-shadow:0 0 0 0 rgba(201,183,135,0);}}
398
  .echart{height:360px;width:100%;}
399
  .echart.tall{height:480px;}
400
  .globe3d{height:520px;width:100%;border-radius:9px;overflow:hidden;border:1px solid var(--gold-line);background:#060606;}
 
442
  <div class="nav-item" data-view="maritime" onclick="go('maritime')"><span class="ico">⚓</span>Maritime Picture</div>
443
  <div class="nav-item" data-view="sanctions" onclick="go('sanctions')"><span class="ico">◴</span>Sanctions &amp; Dark-Vessel</div>
444
 
445
+ <div class="nav-group">Fleet (Vessels)</div>
446
+ <div class="nav-item" data-view="fleet" onclick="go('fleet')"><span class="ico">⛴</span>Fleet Overview</div>
447
+ <div class="nav-item" data-view="fleetmaint" onclick="go('fleetmaint')"><span class="ico">⚒</span>Maintenance &amp; Compliance</div>
448
+ <div class="nav-item" data-view="fleetbrief" onclick="go('fleetbrief')"><span class="ico">◈</span>Fleet Briefings</div>
449
+
450
  <div class="nav-group">Decide &amp; Govern</div>
451
  <div class="nav-item" data-view="roe" onclick="go('roe')"><span class="ico">⊞</span>Engagement Rules</div>
452
  <div class="nav-item" data-view="lambda" onclick="go('lambda')"><span class="ico">◈</span>Trust Score Monitor</div>
 
490
  <script>
491
  const BASE = 'https://szlholdings-killinchu.hf.space';
492
  const API = BASE + '/api/killinchu/v1';
493
+ const FLEET = BASE + '/api/killinchu/v1/fleet'; // GAP-1/GAP-2: commercial-fleet (vessels) surface, served verbatim from platform seed-data.
494
 
495
  // Keep ONE persistent WebGL context alive for the whole session before any 3D view
496
  // initialises. On software-GL (SwiftShader) hosts the GL-context pool is tiny and
 
541
  function barV(id,labels,data,colors){mkChart(id,{type:'bar',data:{labels,datasets:[{data,backgroundColor:colors||TEAL,borderRadius:4}]},options:{scales:{x:{grid:{display:false},ticks:{color:'#9a9a9a',font:{size:9}}},y:{grid:{color:GRID},ticks:{color:DIM}}},plugins:{legend:{display:false}},responsive:true,maintainAspectRatio:false}});}
542
  function lineSpark(id,labels,data,color){mkChart(id,{type:'line',data:{labels,datasets:[{data,borderColor:color||TEAL,backgroundColor:'rgba(95,179,163,0.12)',fill:true,tension:.35,pointRadius:0,borderWidth:1.6}]},options:{scales:{x:{display:false},y:{display:false}},plugins:{legend:{display:false},tooltip:{enabled:false}},responsive:true,maintainAspectRatio:false}});}
543
  function doughnut(id,labels,data,colors){mkChart(id,{type:'doughnut',data:{labels,datasets:[{data,backgroundColor:colors,borderColor:'#0a0a0a',borderWidth:2}]},options:{cutout:'62%',plugins:{legend:{display:false},tooltip:{enabled:true}},responsive:true,maintainAspectRatio:false}});}
544
+ // Forecast line: historical (solid teal) + forecast tail (dashed gold). `actual` and
545
+ // `forecast` are arrays the same length as `labels`; null entries break the line so the
546
+ // two segments meet at the boundary point. Used by Fleet Overview's 4 forecast modules.
547
+ function lineForecast(id,labels,actual,forecast){mkChart(id,{type:'line',data:{labels,datasets:[
548
+ {label:'history',data:actual,borderColor:TEAL,backgroundColor:'rgba(95,179,163,0.10)',fill:true,tension:.3,pointRadius:1.5,borderWidth:1.8,spanGaps:false},
549
+ {label:'forecast',data:forecast,borderColor:GOLD,backgroundColor:'rgba(201,183,135,0.08)',fill:false,tension:.3,pointRadius:2,borderWidth:1.8,borderDash:[5,4],spanGaps:false}]},
550
+ options:{scales:{x:{grid:{color:GRID},ticks:{color:DIM,font:{size:9},maxRotation:0,autoSkip:true,maxTicksLimit:6}},y:{grid:{color:GRID},ticks:{color:DIM,font:{size:9}}}},
551
+ plugins:{legend:{display:true,labels:{color:'#9a9a9a',boxWidth:18,font:{size:9}}},tooltip:{enabled:true}},responsive:true,maintainAspectRatio:false}});}
552
  let _fg=null;
553
  function mesh3d(id,nodes,links,_try){const host=el(id);if(!host||!window.ForceGraph3D)return;host.innerHTML='';try{_fg=ForceGraph3D()(host).backgroundColor('rgba(0,0,0,0)').width(host.clientWidth).height(host.clientHeight).graphData({nodes,links}).nodeLabel('name').nodeColor(n=>n.color||TEAL).nodeVal(n=>n.val||4).linkColor(()=>'rgba(201,183,135,0.45)').linkWidth(1.2).linkDirectionalParticles(2).linkDirectionalParticleSpeed(0.006).linkDirectionalParticleColor(()=>TEAL).showNavInfo(false);setTimeout(()=>{try{_fg.width(host.clientWidth).height(host.clientHeight);}catch(e){}
554
  /* GL pool can be momentarily exhausted on software-GL; if no WebGL canvas appeared, re-init once. */
 
776
  }catch(e){ setVAlertBadge('fail','ERROR'); if(el('valert-detail')) el('valert-detail').textContent='retry: '+e.message; }
777
  }
778
 
779
+ // GAP-2: Voyage Risk Exchange governed-decision loop (signals -> forecast -> evidence
780
+ // -> advisory recommendation w/ rollback -> brief). Ported verbatim from the platform
781
+ // vessels vertical; served by the killinchu server at /fleet/voyage-risk. Trust gate is
782
+ // shown as ADVISORY (Conjecture 1), never a pass/fail oracle.
783
+ async function voyage_risk_run(){
784
+ const host=el('vr-host'); if(!host)return;
785
+ host.innerHTML='<span class="mono dim">running the governed-decision loop…</span>';
786
+ try{
787
+ const d=await getJSON(FLEET+'/voyage-risk');
788
+ setOut('vr-raw',d);
789
+ const sig=(d.signals||[]).map(s=>`<div class="row"><span class="badge b-warn">${esc(s.kind)}</span><span><b>${esc(s.summary)}</b></span><span class="spacer mono dim" style="font-size:10px">${esc(s.source)} · weight ${esc(s.weight)}</span></div>`).join('');
790
+ const fc=d.forecast||{}, rec=d.recommendation||{}, br=d.brief||{};
791
+ const conf=Math.round((rec.confidence||fc.confidence||0)*100);
792
+ host.innerHTML=`
793
+ <div class="kpis" style="margin:.2rem 0 .5rem">
794
+ <div class="kpi"><div class="k">Delay risk</div><div class="v warn">${esc(fc.delay_risk||'—')}</div></div>
795
+ <div class="kpi"><div class="k">Route risk</div><div class="v">${esc(fc.route_risk||'—')}</div></div>
796
+ <div class="kpi"><div class="k">Confidence</div><div class="v teal">${conf}%</div><div class="d">advisory</div></div>
797
+ <div class="kpi"><div class="k">Trust gate</div><div class="v teal">Conjecture</div><div class="d">advisory, not proven</div></div>
798
+ </div>
799
+ <div style="margin:.3rem 0"><b style="color:#c9b787">1 · Signals</b></div>${sig}
800
+ <div style="margin:.5rem 0 .2rem"><b style="color:#c9b787">2 · Forecast</b> <span class="mono dim" style="font-size:10px">${esc(fc.method||'')}</span></div>
801
+ <div style="margin:.4rem 0 .2rem"><b style="color:#c9b787">3 · Evidence</b> <span class="mono dim">${(d.evidence||[]).length} item(s) linked to signals</span></div>
802
+ <div class="card" style="margin:.4rem 0;border-color:rgba(201,183,135,.35)">
803
+ <div class="card-h"><span class="card-t">4 · Recommendation (advisory)</span><span class="badge b-warn">REQUIRES HUMAN APPROVAL</span></div>
804
+ <div style="line-height:1.7"><b>${esc(rec.title||'')}</b></div>
805
+ <div class="mono dim" style="font-size:11px;line-height:1.7;margin-top:.3rem">
806
+ Next action: ${esc(rec.next_action||'')}<br>
807
+ <span style="color:#c9a05f">Rollback path:</span> ${esc(rec.rollback_path||'')}<br>
808
+ Evidence: ${esc((rec.evidence_ids||[]).join(', '))}<br>
809
+ Owner: ${esc(rec.owner||'')} · input/output class: ${esc(rec.input_class||'')} → ${esc(rec.output_class||'')}
810
+ </div></div>
811
+ <div style="margin:.3rem 0"><b style="color:#c9b787">5 · Brief</b> <span class="mono dim">${esc(br.headline||'')}</span></div>
812
+ <div class="legend"><span>${esc(d.honesty||'Advisory recommendation — the trust score is a conjecture, not a proven oracle.')}</span></div>`;
813
+ }catch(e){host.innerHTML='<span class="mono dim">retry: '+esc(e.message)+'</span>';setOut('vr-raw','retry: '+e.message);}
814
+ }
815
+
816
 
817
  // ===================== VIEWS =====================
818
  const VIEWS = {
 
923
  <div class="btns"><button class="btn teal" onclick="vessel_owner()">▶ Trace ownership</button></div>
924
  <div id="own-host"><div class="graph3d" id="owner-3d" style="height:320px"></div></div>
925
  <div class="legend"><span>Ownership chain: registered operator → shell companies → ultimate owner. Sample graph for the demo data set.</span></div></div>
926
+ <div class="card"><div class="card-h"><span class="card-t">Voyage Risk Exchange — governed decision</span><span class="card-ep">signals → forecast → evidence → recommendation</span></div>
927
+ <div class="btns"><button class="btn teal" onclick="voyage_risk_run()">▶ Run the voyage-risk decision loop</button></div>
928
+ <div id="vr-host" class="mono dim" style="font-size:12px;line-height:1.7">A governed-decision loop, ported from the platform vessels vertical: it collects voyage signals, forecasts risk, attaches evidence, then issues an <b>advisory</b> recommendation with a rollback path and a genuinely signed receipt. The trust gate is a documented conjecture, not a pass/fail oracle.</div>
929
+ <details class="raw"><summary>raw /fleet/voyage-risk</summary><pre class="out" id="vr-raw">—</pre></details></div>
930
  <details class="raw"><summary>raw AIS replay sample set</summary><pre class="out" id="maritime-raw">—</pre></details>
931
  <div class="honesty"><b>Honest by design.</b> This maritime picture is an <b>AIS replay — a sample track set, not a live feed</b> (no AIS provider is wired). Sanctions and ownership are screened against a small <b>sample list</b> in OFAC/UN/EU format, not full real-time coverage. Vessel-alert receipts are <b>genuinely signed</b> with killinchu's real key and verifiable offline — the same signed-receipt thesis as the drone side.</div>`;
932
  setOut('maritime-raw', V);
 
978
  el('sanc-pick').addEventListener('change',()=>{const v=V.find(x=>x.id===el('sanc-pick').value); if(v) sanctions_drawGap(v);});
979
  }},
980
 
981
+ // ══════════════════ FLEET (Vessels) — GAP-1 commercial-fleet surface ══════════════════
982
+ // Real platform seed-data/vessels/* served verbatim from the killinchu server.
983
+ // MANDATORY honesty label on every fleet tab. No mock data.
984
+ // ── Fleet Overview ──────────────────────────────────────────────
985
+ fleet:{title:'Fleet Overview',badge:'18 VESSELS · SAMPLE DATASET',sub:'Commercial fleet at a glance — each ship with its carbon-intensity (CII) rating, hull and engine health, time-charter earnings (TCE), utilisation and EEXI, plus four forecast lines (earnings, carbon intensity, utilisation, Baltic Dry Index). Sample fleet dataset — not a live AIS / class-society feed.',
986
+ render:async(c)=>{
987
+ c.innerHTML=`<div class="kpis">
988
+ <div class="kpi"><div class="k">Vessels</div><div class="v" id="f-count">—</div><div class="d">in sample fleet</div></div>
989
+ <div class="kpi"><div class="k">Avg hull health</div><div class="v teal" id="f-hull">—</div><div class="d">condition index</div></div>
990
+ <div class="kpi"><div class="k">Poor CII (D/E)</div><div class="v warn" id="f-cii">—</div><div class="d">carbon-intensity risk</div></div>
991
+ <div class="kpi"><div class="k">Receipts</div><div class="v teal">Signed</div><div class="d">verify offline</div></div>
992
+ </div>
993
+ <div class="card"><div class="card-h"><span class="card-t">Forecast modules</span><span class="card-ep">history (teal) + forecast (dashed gold)</span></div>
994
+ <div class="grid2" id="f-fc-grid">
995
+ <div><div class="mono dim" id="f-fc-t0" style="font-size:11px;margin-bottom:.2rem">—</div><div class="chartbox"><canvas id="f-fc-0"></canvas></div></div>
996
+ <div><div class="mono dim" id="f-fc-t1" style="font-size:11px;margin-bottom:.2rem">—</div><div class="chartbox"><canvas id="f-fc-1"></canvas></div></div>
997
+ <div><div class="mono dim" id="f-fc-t2" style="font-size:11px;margin-bottom:.2rem">—</div><div class="chartbox"><canvas id="f-fc-2"></canvas></div></div>
998
+ <div><div class="mono dim" id="f-fc-t3" style="font-size:11px;margin-bottom:.2rem">—</div><div class="chartbox"><canvas id="f-fc-3"></canvas></div></div>
999
+ </div>
1000
+ <div class="legend"><span>Each module carries a forecast point and a confidence %. Forecast values are model estimates on sample data, not guarantees.</span></div></div>
1001
+ <div class="card"><div class="card-h"><span class="card-t">CII rating mix</span><span class="card-ep">A (best) → D (worst)</span></div>
1002
+ <div class="chartbox"><canvas id="f-cii-bar"></canvas></div></div>
1003
+ <div class="card"><div class="card-h"><span class="card-t">Fleet register</span><span class="card-ep" id="f-reg-ep">—</span></div><div id="f-list"><div class="row mono dim">loading…</div></div></div>
1004
+ <details class="raw"><summary>raw /fleet/vessels + /fleet/forecast-modules</summary><pre class="out" id="f-raw">—</pre></details>
1005
+ <div class="honesty"><b>Honest by design.</b> <b>Sample fleet dataset — not a live AIS/class-society feed.</b> These 18 vessels and the four forecast modules are real, citable demo content served verbatim from the SZL platform dataset (seed-data/vessels/*), not a real-time provider. The trust score is a documented <b>conjecture</b>, not a proven guarantee; receipts are <b>genuinely signed</b> (ECDSA P-256) and verifiable offline.</div>`;
1006
+ try{
1007
+ const [vr,fr] = await Promise.all([getJSON(FLEET+'/vessels'),getJSON(FLEET+'/forecast-modules')]);
1008
+ const V = vr.data||[], M = fr.data||[];
1009
+ setOut('f-raw',{vessels:V.length,forecast_modules:M.length,honesty:vr.honesty,sample:V.slice(0,1)});
1010
+ const poorCII = V.filter(v=>['D','E'].includes(String(v.ciiRating||'').toUpperCase())).length;
1011
+ const avgHull = V.length?Math.round(V.reduce((a,v)=>a+(v.hullCondition||0),0)/V.length):0;
1012
+ el('f-count').textContent=V.length; el('f-hull').textContent=avgHull; el('f-cii').textContent=poorCII;
1013
+ // 4 forecast modules → history+forecast lines
1014
+ M.slice(0,4).forEach((m,i)=>{
1015
+ const tt=el('f-fc-t'+i); if(tt) tt.innerHTML='<b style="color:#c9b787">'+esc(m.title)+'</b> · '+esc(m.metric)+' · forecast '+esc(m.forecastValue)+' ('+esc(m.confidence)+'% conf, '+esc(m.trend)+')';
1016
+ const pts=m.dataPoints||[]; const labels=pts.map(p=>p.date);
1017
+ // history = points without a forecast field; forecast = points WITH forecast (plus boundary point to connect)
1018
+ let firstFcst=pts.findIndex(p=>p.forecast!=null);
1019
+ const actual=pts.map((p,j)=> (firstFcst<0||j<firstFcst)? p.value : (j===firstFcst? p.value : null));
1020
+ const fcst=pts.map((p,j)=> (firstFcst>=0 && j>=firstFcst-1 && j>=0 && (p.forecast!=null|| j===firstFcst-1))? p.value : null);
1021
+ lineForecast('f-fc-'+i,labels,actual,fcst);
1022
+ });
1023
+ // CII mix bar
1024
+ const order=['A','B','C','D','E']; const counts=order.map(r=>V.filter(v=>String(v.ciiRating||'').toUpperCase()===r).length);
1025
+ const cols=['#5fb3a3','#7fb98f','#c9b787','#c9a05f','#b06a5a'];
1026
+ barV('f-cii-bar',order,counts,cols);
1027
+ // register table
1028
+ const h=el('f-list'); h.innerHTML=''; el('f-reg-ep').textContent=V.length+' vessels';
1029
+ V.forEach(v=>{
1030
+ const r=String(v.ciiRating||'').toUpperCase();
1031
+ const rc = (r==='A'||r==='B')?'b-live':(r==='C')?'b-warn':'b-err';
1032
+ h.insertAdjacentHTML('beforeend',`<div class="row">
1033
+ <span class="badge ${rc}">CII ${esc(r)}</span>
1034
+ <span><b>${esc(v.name)}</b> <span class="mono dim" style="font-size:10px">IMO ${esc(v.imo)}</span></span>
1035
+ <span class="mono dim" style="font-size:11px">${esc(v.vesselType)} · ${esc(v.shipClass)} · ${esc(v.flag)}</span>
1036
+ <span class="spacer mono dim" style="font-size:10px">hull ${v.hullCondition??'?'} · eng ${v.engineHealth??'?'} · TCE $${(v.tce||0).toLocaleString()} · util ${v.utilization??'?'}% · EEXI ${v.eexi??'?'} · ${esc(v.operator)} · ${esc(v.classificationSociety)}</span>
1037
+ </div>`);
1038
+ });
1039
+ if(!V.length) h.innerHTML='<div class="row mono dim">no vessels</div>';
1040
+ }catch(e){setHTML('f-list','<div class="row mono dim">retry: '+esc(e.message)+'</div>');setOut('f-raw','retry: '+e.message);}
1041
+ }},
1042
+
1043
+ // ── Maintenance & Compliance ────────────────────────────────────
1044
+ fleetmaint:{title:'Maintenance & Compliance',badge:'PREDICTIVE · CERTS · PSC',sub:'Where the fleet needs attention: predicted component failures (probability, date, cost, risk), certificates expiring soon, and port-state-control deficiencies with their SOLAS / MARPOL citations. Sample fleet dataset — not a live AIS / class-society feed.',
1045
+ render:async(c)=>{
1046
+ c.innerHTML=`<div class="kpis">
1047
+ <div class="kpi"><div class="k">High-risk components</div><div class="v err" id="fm-high">—</div><div class="d">predicted failure</div></div>
1048
+ <div class="kpi"><div class="k">Certs expiring/expired</div><div class="v warn" id="fm-cert">—</div><div class="d">need renewal</div></div>
1049
+ <div class="kpi"><div class="k">Open PSC deficiencies</div><div class="v warn" id="fm-psc">—</div><div class="d">Paris/Tokyo/Riyadh MOU</div></div>
1050
+ <div class="kpi"><div class="k">Receipts</div><div class="v teal">Signed</div><div class="d">verify offline</div></div>
1051
+ </div>
1052
+ <div class="card"><div class="card-h"><span class="card-t">Predictive maintenance — failure probability</span><span class="card-ep">per component</span></div>
1053
+ <div class="chartbox tall"><canvas id="fm-pm-bar"></canvas></div>
1054
+ <div id="fm-pm-list"><div class="row mono dim">loading…</div></div></div>
1055
+ <div class="grid2">
1056
+ <div class="card"><div class="card-h"><span class="card-t">Certificates expiring soon</span></div><div id="fm-cert-list"><div class="row mono dim">loading…</div></div></div>
1057
+ <div class="card"><div class="card-h"><span class="card-t">Port-state-control deficiencies</span></div><div id="fm-psc-list"><div class="row mono dim">loading…</div></div></div>
1058
+ </div>
1059
+ <details class="raw"><summary>raw /fleet/predictive-maintenance · /compliance-certificates · /port-state-deficiencies</summary><pre class="out" id="fm-raw">—</pre></details>
1060
+ <div class="honesty"><b>Honest by design.</b> <b>Sample fleet dataset — not a live AIS/class-society feed.</b> Predicted failures, certificate dates and port-state deficiencies are real, citable demo content from the SZL platform dataset (seed-data/vessels/*), not live class-society or MOU records. Receipts are <b>genuinely signed</b> and verifiable offline.</div>`;
1061
+ try{
1062
+ const [pm,cc,psd]=await Promise.all([getJSON(FLEET+'/predictive-maintenance'),getJSON(FLEET+'/compliance-certificates'),getJSON(FLEET+'/port-state-deficiencies')]);
1063
+ const PM=pm.data||[],CC=cc.data||[],PSD=psd.data||[];
1064
+ setOut('fm-raw',{predictive_maintenance:PM.length,certificates:CC.length,deficiencies:PSD.length,honesty:pm.honesty});
1065
+ el('fm-high').textContent=PM.filter(p=>String(p.riskLevel||'').toLowerCase()==='high').length;
1066
+ el('fm-cert').textContent=CC.filter(c=>['Expiring Soon','Expired'].includes(c.status)).length;
1067
+ el('fm-psc').textContent=PSD.filter(p=>String(p.status||'').toLowerCase()==='open').length;
1068
+ // predictive maintenance bar (failure probability), colored by risk
1069
+ const labs=PM.map(p=>p.component.length>26?p.component.slice(0,24)+'…':p.component);
1070
+ const vals=PM.map(p=>p.failureProbability||0);
1071
+ const rc=r=>r==='High'?RED:r==='Medium'?WARN:TEAL;
1072
+ barH('fm-pm-bar',labs,vals,PM.map(p=>rc(p.riskLevel)));
1073
+ const ph=el('fm-pm-list'); ph.innerHTML='';
1074
+ PM.forEach(p=>{const cls=p.riskLevel==='High'?'b-err':p.riskLevel==='Medium'?'b-warn':'b-live';
1075
+ ph.insertAdjacentHTML('beforeend',`<div class="row">
1076
+ <span class="badge ${cls}">${esc(p.riskLevel)} · ${p.failureProbability}%</span>
1077
+ <span><b>${esc(p.vesselName)}</b> — ${esc(p.component)}</span>
1078
+ <span class="spacer mono dim" style="font-size:10px">by ${esc(p.predictedFailureDate)} · est $${(p.estimatedCost||0).toLocaleString()} · ${esc(p.recommendedAction)}</span>
1079
+ </div>`);});
1080
+ const ch=el('fm-cert-list'); ch.innerHTML='';
1081
+ CC.sort((a,b)=>(a.daysUntilExpiry??9999)-(b.daysUntilExpiry??9999)).forEach(cert=>{
1082
+ const cls=cert.status==='Expired'?'b-err':cert.status==='Expiring Soon'?'b-warn':'b-live';
1083
+ ch.insertAdjacentHTML('beforeend',`<div class="row">
1084
+ <span class="badge ${cls}">${esc(cert.status)}</span>
1085
+ <span><b>${esc(cert.vesselName)}</b> — ${esc(cert.certificateType)}</span>
1086
+ <span class="spacer mono dim" style="font-size:10px">${esc(cert.issuer)} · exp ${esc(cert.expiryDate)} (${cert.daysUntilExpiry}d) · ${esc(cert.regulation)}</span>
1087
+ </div>`);});
1088
+ const sh=el('fm-psc-list'); sh.innerHTML='';
1089
+ PSD.forEach(p=>{const cls=p.severity==='High'?'b-err':p.severity==='Medium'?'b-warn':'b-live';
1090
+ sh.insertAdjacentHTML('beforeend',`<div class="row">
1091
+ <span class="badge ${cls}">${esc(p.severity)} · ${esc(p.status)}</span>
1092
+ <span><b>${esc(p.vesselName)}</b> <span class="mono dim" style="font-size:10px">code ${esc(p.deficiencyCode)} · ${esc(p.mouRegime)} · ${esc(p.port)}</span></span>
1093
+ <span class="spacer mono dim" style="font-size:10px">${esc(p.description)}</span>
1094
+ </div>`);});
1095
+ }catch(e){setHTML('fm-pm-list','<div class="row mono dim">retry: '+esc(e.message)+'</div>');setOut('fm-raw','retry: '+e.message);}
1096
+ }},
1097
+
1098
+ // ── Fleet Briefings ─────────────────────────────────────────────
1099
+ fleetbrief:{title:'Fleet Briefings',badge:'REGULATION-CITED',sub:'AI-generated fleet briefings, each citing the regulation behind it (MARPOL Annex VI, IMO MEPC.352/355(78)), with a confidence %, the affected vessels, and concrete action items. Sample fleet dataset — not a live AIS / class-society feed.',
1100
+ render:async(c)=>{
1101
+ c.innerHTML=`<div class="kpis">
1102
+ <div class="kpi"><div class="k">Briefings</div><div class="v" id="fb-count">—</div><div class="d">in sample set</div></div>
1103
+ <div class="kpi"><div class="k">Critical</div><div class="v err" id="fb-crit">—</div><div class="d">highest severity</div></div>
1104
+ <div class="kpi"><div class="k">Avg confidence</div><div class="v teal" id="fb-conf">—</div><div class="d">advisory, not proven</div></div>
1105
+ <div class="kpi"><div class="k">Trust gate</div><div class="v teal">Conjecture</div><div class="d">advisory, not proven</div></div>
1106
+ </div>
1107
+ <div id="fb-cards"><div class="row mono dim">loading…</div></div>
1108
+ <details class="raw"><summary>raw /fleet/ai-briefings</summary><pre class="out" id="fb-raw">—</pre></details>
1109
+ <div class="honesty"><b>Honest by design.</b> <b>Sample fleet dataset — not a live AIS/class-society feed.</b> These briefings are real, citable demo content from the SZL platform dataset (seed-data/vessels/*). Confidence is an <b>advisory</b> figure (the trust score is a documented conjecture, not a proven oracle), and the regulatory citations point to the genuine instruments (MARPOL Annex VI, IMO MEPC.352/355(78)).</div>`;
1110
+ try{
1111
+ const br=await getJSON(FLEET+'/ai-briefings'); const B=br.data||[];
1112
+ setOut('fb-raw',{briefings:B.length,honesty:br.honesty});
1113
+ el('fb-count').textContent=B.length;
1114
+ el('fb-crit').textContent=B.filter(b=>String(b.severity||'').toLowerCase()==='critical').length;
1115
+ el('fb-conf').textContent=B.length?Math.round(B.reduce((a,b)=>a+(b.confidence||0),0)/B.length)+'%':'—';
1116
+ const h=el('fb-cards'); h.innerHTML='';
1117
+ B.forEach(b=>{
1118
+ const sev=String(b.severity||'').toLowerCase();
1119
+ const sc = sev==='critical'?'b-err':sev==='warning'?'b-warn':'b-live';
1120
+ const ai=(b.actionItems||[]).map(a=>'<li>'+esc(a)+'</li>').join('');
1121
+ const av=(b.affectedVessels||[]).map(v=>'<span class="badge b-teal" style="margin:1px">'+esc(v)+'</span>').join(' ');
1122
+ h.insertAdjacentHTML('beforeend',`<div class="card">
1123
+ <div class="card-h"><span class="card-t">${esc(b.title)}</span><span class="badge ${sc}">${esc(b.severity)} · ${esc(b.category)}</span></div>
1124
+ <div style="margin:.3rem 0;line-height:1.6">${esc(b.summary)}</div>
1125
+ <div class="mono dim" style="font-size:11px;line-height:1.7;margin:.4rem 0">${esc(b.details)}</div>
1126
+ <div style="margin:.4rem 0"><b style="color:#c9b787;font-size:12px">Action items</b><ul style="margin:.2rem 0 .2rem 1.1rem;font-size:12px;line-height:1.6">${ai}</ul></div>
1127
+ <div style="margin:.3rem 0;font-size:11px"><span class="mono dim">Affected:</span> ${av}</div>
1128
+ <div class="legend"><span>Confidence ${esc(b.confidence)}% (advisory) · generated ${esc((b.generatedAt||'').slice(0,10))}</span></div>
1129
+ </div>`);
1130
+ });
1131
+ if(!B.length) h.innerHTML='<div class="row mono dim">no briefings</div>';
1132
+ }catch(e){setHTML('fb-cards','<div class="row mono dim">retry: '+esc(e.message)+'</div>');setOut('fb-raw','retry: '+e.message);}
1133
+ }},
1134
+
1135
  // ── 3.4 ROE Policy Editor ────────────────────────────────────────
1136
  roe:{title:'Engagement Rules',badge:'LIVE POLICY',sub:'The rules that decide whether a drone may be engaged — speed limits, required ID, allowed directions. Test a track against the current rules; every check produces a genuinely signed receipt.',
1137
  render:async(c)=>{
 
1265
  <div class="btns"><button class="btn teal" onclick="bft_exec()">▶ Execute demo mission</button></div>
1266
  <div id="bft-exec-summary" class="mono dim" style="font-size:12px;margin-bottom:.5rem">— click to execute —</div>
1267
  <details class="raw"><summary>raw /uds/v1/mission/execute</summary><pre class="out" id="bft-exec-out">—</pre></details></div>
1268
+ <div id="bft-note" class="mono dim" style="font-size:12px;margin:.4rem 0"></div>
1269
  <details class="raw"><summary>raw /uds/v1/healthz</summary><pre class="out" id="bft-raw">—</pre></details>
1270
  ${HONEST}`;
1271
  try{
1272
  const d = await getJSON(BASE+'/api/killinchu/uds/v1/healthz');
1273
  setOut('bft-raw',d);
1274
+ const q = d.quorum || {};
 
1275
  const organs=Object.entries(d.organs||{});
1276
  let online=0; organs.forEach(([,o])=>{if(o.status==='ok')online++;});
1277
+ const total = (q.total!=null)?q.total:organs.length;
1278
+ const needed = (q.needed!=null)?q.needed:Math.floor(total/2)+1;
1279
+ // Consensus holds when a strict majority is reachable. Critically this does
1280
+ // NOT require amaru — quorum_without_amaru proves the system survives amaru
1281
+ // being fully decommissioned. Fall back to quorum_possible / online math.
1282
+ const holds = (q.quorum_without_amaru!=null) ? q.quorum_without_amaru
1283
+ : (q.quorum_possible!=null) ? q.quorum_possible
1284
+ : (d.quorum_possible!=null) ? d.quorum_possible
1285
+ : (online>=needed);
1286
+ el('k-quorum').textContent = holds ? 'CONSENSUS HOLDS' : 'NO QUORUM';
1287
+ el('k-quorum').className = 'v '+(holds?'live':'warn');
1288
  if(el('bft-count')) el('bft-count').textContent=online;
1289
+ doughnut('bft-donut',['online','unreachable'],[online,Math.max(0,total-online)],[TEAL,RED]);
1290
+ // Honest messaging: explain WHY it still holds even if a node is offline.
1291
+ const amaruOff = q.amaru_offline===true || (d.organs&&d.organs.amaru&&d.organs.amaru.status!=='ok');
1292
+ let note='';
1293
+ if(holds && amaruOff){
1294
+ note = `<span class="badge b-live">CONSENSUS HOLDS</span> ${online} of ${total} systems online — a majority (need ${needed}) is reachable. The Cortex node (amaru) is offline and is being decommissioned; consensus does <b>not</b> depend on it.`;
1295
+ } else if(holds){
1296
+ note = `<span class="badge b-live">CONSENSUS HOLDS</span> ${online} of ${total} systems online — majority threshold of ${needed} met.`;
1297
+ } else {
1298
+ note = `<span class="badge b-warn">NO QUORUM</span> only ${online} of ${total} systems online — need at least ${needed} to proceed. Actions are held (fail-closed).`;
1299
+ }
1300
+ if(el('bft-note')) el('bft-note').innerHTML = note;
1301
  const h = el('bft-organs'); h.innerHTML='';
1302
  organs.forEach(([name,o])=>{
1303
  const ok = o.status==='ok';
1304
+ const optional = o.essential===false;
1305
+ const stTxt = ok ? 'ONLINE' : (esc(String(o.status||'').toUpperCase())||'DOWN');
1306
+ const badgeCls = ok ? 'b-live' : (optional ? 'b-warn' : 'b-err');
1307
+ const tag = optional ? ' <span class="mono dim" style="font-size:10px">(optional · being retired)</span>' : '';
1308
  h.insertAdjacentHTML('beforeend',`<div class="row">
1309
+ <span class="badge ${badgeCls}">${stTxt}</span>
1310
+ <span>${esc(name)}${tag}</span>
1311
  <span class="spacer mono dim">${o.local?'local':''} ${o.http?'HTTP '+o.http:''} ${o.latency_ms?Math.round(o.latency_ms)+'ms':''}</span>
1312
  </div>`);
1313
  });
 
1567
  <div class="kpi"><div class="k">Organs reachable</div><div class="v live" id="org-reach">probing…</div></div>
1568
  <div class="kpi"><div class="k">Links to brain</div><div class="v teal" id="org-links">—</div></div>
1569
  <div class="kpi"><div class="k">Signed spans</div><div class="v" id="org-spans">—</div><div class="d">live audit depth</div></div></div>
1570
+ <div class="card"><div class="card-h"><span class="card-t">The brain at the center — live 3D</span><span class="card-ep">drag to orbit · particles = live links</span></div><div class="graph3d hero" id="org-3d"><div class="org-loading" id="org-loading"><div class="org-pulse"></div><div class="mono dim" style="margin-top:.8rem;font-size:12px">probing live organism… connecting to shared brain</div></div></div><div class="brain-note">a11oy is the orchestrator brain: every edge terminates at the gold core. killinchu is the field/Navy surface sharing that brain. Health from the live observability probe.</div></div>
1571
  <div class="card"><div class="card-h"><span class="card-t">Organ health detail</span><span class="card-ep">live probe</span></div><div id="org-host"><div class="row mono dim">probing…</div></div>
1572
  <details class="raw"><summary>raw /observability/summary</summary><pre class="out" id="org-raw">loading…</pre></details></div>${HONEST}`;window.organism_load();}},
1573
 
killinchu_fleet_vessels.py ADDED
@@ -0,0 +1,243 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ killinchu_fleet_vessels.py — FLEET (Vessels) commercial-fleet surface (GAP-1 + GAP-2).
3
+
4
+ ADDITIVE module. serve.py imports this and calls register(app) AFTER the other
5
+ routers but BEFORE the SPA catch-all. It does NOT touch any existing route.
6
+
7
+ GAP-1 — serve the REAL platform seed-data/vessels/* datasets VERBATIM as static
8
+ JSON endpoints under /api/killinchu/v1/fleet/*. These are clearly-labelled SAMPLE
9
+ fleet data, NOT a live AIS / class-society feed.
10
+
11
+ GET /api/killinchu/v1/fleet/vessels -> seed-data/vessels/vessels.json
12
+ GET /api/killinchu/v1/fleet/forecast-modules -> seed-data/vessels/forecast-modules.json
13
+ GET /api/killinchu/v1/fleet/predictive-maintenance -> seed-data/vessels/predictive-maintenance.json
14
+ GET /api/killinchu/v1/fleet/compliance-certificates -> seed-data/vessels/compliance-certificates.json
15
+ GET /api/killinchu/v1/fleet/port-state-deficiencies -> seed-data/vessels/port-state-deficiencies.json
16
+ GET /api/killinchu/v1/fleet/ai-briefings -> seed-data/vessels/ai-briefings.json
17
+ GET /api/killinchu/v1/fleet/all -> {datasets..., honesty}
18
+
19
+ GAP-2 — the vessels vertical "Voyage Risk Exchange" governed-decision loop, ported
20
+ VERBATIM (pure functions) from platform services/verticals/vessels/{signals,forecast,
21
+ evidence,recommendations,brief}.py + contracts.py.
22
+
23
+ GET /api/killinchu/v1/fleet/voyage-risk -> full 5-stage loop result
24
+ (signals -> forecast -> evidence
25
+ -> recommendation w/ rollback -> brief)
26
+
27
+ Honesty doctrine (absolute): trust score Λ = Conjecture 1 (NOT a theorem); the
28
+ Recommendation is ADVISORY with a human-approval flag and a rollback path; the
29
+ datasets are sample/recorded, not a live feed.
30
+
31
+ Source provenance: github.com/szl-holdings/platform seed-data/vessels/* (datasets)
32
+ and services/verticals/vessels/* (decision loop), fetched via the GitHub contents
33
+ API and embedded verbatim.
34
+ """
35
+ from __future__ import annotations
36
+
37
+ import json
38
+ import os
39
+ from typing import Any
40
+
41
+ try:
42
+ from fastapi import APIRouter
43
+ from fastapi.responses import JSONResponse
44
+ except Exception: # pragma: no cover
45
+ APIRouter = None # type: ignore
46
+ JSONResponse = None # type: ignore
47
+
48
+ _HERE = os.path.dirname(os.path.abspath(__file__))
49
+ _DATA_PATH = os.path.join(_HERE, "fleet_vessels_data.json")
50
+
51
+ HONESTY_LABEL = "Sample fleet dataset — not a live AIS/class-society feed."
52
+
53
+ # ---------------------------------------------------------------------------
54
+ # Dataset loader — read the embedded verbatim platform seed-data once.
55
+ # ---------------------------------------------------------------------------
56
+ _DATASETS: dict[str, Any] = {}
57
+
58
+
59
+ def _load() -> dict[str, Any]:
60
+ global _DATASETS
61
+ if _DATASETS:
62
+ return _DATASETS
63
+ try:
64
+ with open(_DATA_PATH, "r", encoding="utf-8") as fh:
65
+ _DATASETS = json.load(fh)
66
+ except Exception:
67
+ _DATASETS = {
68
+ "vessels": [],
69
+ "forecast-modules": [],
70
+ "predictive-maintenance": [],
71
+ "compliance-certificates": [],
72
+ "port-state-deficiencies": [],
73
+ "ai-briefings": [],
74
+ }
75
+ return _DATASETS
76
+
77
+
78
+ # ---------------------------------------------------------------------------
79
+ # GAP-2 — Voyage Risk Exchange governed-decision loop (ported verbatim).
80
+ # Pure functions, no network. Mirrors platform services/verticals/vessels/*.
81
+ # ---------------------------------------------------------------------------
82
+ def _signals_collect() -> list[dict[str, object]]:
83
+ return [
84
+ {
85
+ "id": "sig_vessels_eta_drift",
86
+ "source": "ais",
87
+ "kind": "delay_risk",
88
+ "summary": "ETA drift +18h on charter VL-7714 since last port call",
89
+ "weight": 0.8,
90
+ },
91
+ {
92
+ "id": "sig_vessels_route_advisory",
93
+ "source": "weather",
94
+ "kind": "route_risk",
95
+ "summary": "Beaufort 8 advisory active on planned routing window",
96
+ "weight": 0.7,
97
+ },
98
+ {
99
+ "id": "sig_vessels_compliance_check",
100
+ "source": "compliance",
101
+ "kind": "compliance_gap",
102
+ "summary": "Sanctions screening not refreshed in last 14 days for counterparty",
103
+ "weight": 0.85,
104
+ },
105
+ ]
106
+
107
+
108
+ def _forecast_compute(signals: list[dict[str, Any]]) -> dict[str, Any]:
109
+ return {
110
+ "horizon": "next_voyage",
111
+ "method": "voyage-risk-baseline-v0",
112
+ "delay_risk": "elevated",
113
+ "route_risk": "moderate",
114
+ "claims_risk_placeholder": "watch",
115
+ "confidence": 0.6,
116
+ }
117
+
118
+
119
+ def _evidence_gather(signals: list[dict[str, Any]]) -> list[dict[str, Any]]:
120
+ return [
121
+ {
122
+ "id": f"ev_vessels_{s['id']}",
123
+ "from_signal": s["id"],
124
+ "source": s["source"],
125
+ "claim": s["summary"],
126
+ }
127
+ for s in signals
128
+ ]
129
+
130
+
131
+ def _recommendation_build(
132
+ *, signals: list[dict[str, Any]], forecast: dict[str, Any], evidence: list[dict[str, Any]]
133
+ ) -> dict[str, Any]:
134
+ # Mirrors contracts.Recommendation dataclass shape (to_dict()).
135
+ return {
136
+ "id": "rec_vessels_refresh_sanctions_screen",
137
+ "vertical": "vessels",
138
+ "title": "Refresh sanctions screening for counterparty before bunkering",
139
+ "owner": "vessels-ops@szl",
140
+ "confidence": float(forecast.get("confidence", 0.6)),
141
+ "evidence_ids": [e["id"] for e in evidence],
142
+ "next_action": "Re-run sanctions screen and document refresh in voyage flight recorder.",
143
+ "rollback_path": "If counterparty fails refreshed screen, hold bunkering and escalate to compliance.",
144
+ "requires_human_approval": True,
145
+ "input_class": "vessels_voyage_signals_v1",
146
+ "output_class": "voyage_risk_recommendation_v1",
147
+ }
148
+
149
+
150
+ def _brief_synthesise(
151
+ *,
152
+ signals: list[dict[str, Any]],
153
+ forecast: dict[str, Any],
154
+ evidence: list[dict[str, Any]],
155
+ recommendation: dict[str, Any],
156
+ ) -> dict[str, Any]:
157
+ return {
158
+ "headline": recommendation["title"],
159
+ "delay_risk": forecast.get("delay_risk"),
160
+ "route_risk": forecast.get("route_risk"),
161
+ "claims_risk_placeholder": forecast.get("claims_risk_placeholder"),
162
+ "evidence_count": len(evidence),
163
+ "next_action": recommendation["next_action"],
164
+ }
165
+
166
+
167
+ def voyage_risk_loop() -> dict[str, Any]:
168
+ """Run the full 5-stage governed-decision loop and return every stage."""
169
+ signals = _signals_collect()
170
+ forecast = _forecast_compute(signals)
171
+ evidence = _evidence_gather(signals)
172
+ recommendation = _recommendation_build(signals=signals, forecast=forecast, evidence=evidence)
173
+ brief = _brief_synthesise(
174
+ signals=signals, forecast=forecast, evidence=evidence, recommendation=recommendation
175
+ )
176
+ return {
177
+ "stages": ["signals", "forecast", "evidence", "recommendation", "brief"],
178
+ "signals": signals,
179
+ "forecast": forecast,
180
+ "evidence": evidence,
181
+ "recommendation": recommendation,
182
+ "brief": brief,
183
+ "trust_gate": {
184
+ "lambda_status": "Conjecture 1 (advisory, NOT a theorem)",
185
+ "advisory": True,
186
+ },
187
+ "honesty": (
188
+ "Voyage Risk Exchange — governed-decision loop ported verbatim from the "
189
+ "platform vessels vertical (signals -> forecast -> evidence -> recommendation "
190
+ "-> brief). The recommendation is ADVISORY and requires human approval; the "
191
+ "trust score is a documented Conjecture, not a proven oracle. Sample inputs."
192
+ ),
193
+ "source": "platform services/verticals/vessels/{signals,forecast,evidence,recommendations,brief}.py",
194
+ }
195
+
196
+
197
+ # ---------------------------------------------------------------------------
198
+ # Route registration (ADDITIVE).
199
+ # ---------------------------------------------------------------------------
200
+ def register(app) -> dict[str, Any]:
201
+ if APIRouter is None:
202
+ return {"module": "killinchu_fleet_vessels", "registered_count": 0, "error": "fastapi missing"}
203
+
204
+ data = _load()
205
+ router = APIRouter()
206
+ base = "/api/killinchu/v1/fleet"
207
+ registered: list[str] = []
208
+
209
+ def _serve(key: str):
210
+ async def _h() -> JSONResponse:
211
+ return JSONResponse(
212
+ {"data": data.get(key, []), "honesty": HONESTY_LABEL, "source_key": key}
213
+ )
214
+ return _h
215
+
216
+ for key, path in [
217
+ ("vessels", "/vessels"),
218
+ ("forecast-modules", "/forecast-modules"),
219
+ ("predictive-maintenance", "/predictive-maintenance"),
220
+ ("compliance-certificates", "/compliance-certificates"),
221
+ ("port-state-deficiencies", "/port-state-deficiencies"),
222
+ ("ai-briefings", "/ai-briefings"),
223
+ ]:
224
+ router.add_api_route(f"{base}{path}", _serve(key), methods=["GET"])
225
+ registered.append(f"{base}{path}")
226
+
227
+ @router.get(f"{base}/all")
228
+ async def _all() -> JSONResponse:
229
+ return JSONResponse({
230
+ "datasets": data,
231
+ "counts": {k: (len(v) if isinstance(v, list) else None) for k, v in data.items()},
232
+ "honesty": HONESTY_LABEL,
233
+ "source": "github.com/szl-holdings/platform seed-data/vessels/*",
234
+ })
235
+ registered.append(f"{base}/all")
236
+
237
+ @router.get(f"{base}/voyage-risk")
238
+ async def _voyage() -> JSONResponse:
239
+ return JSONResponse(voyage_risk_loop())
240
+ registered.append(f"{base}/voyage-risk")
241
+
242
+ app.include_router(router)
243
+ return {"module": "killinchu_fleet_vessels", "registered_count": len(registered), "routes": registered}
killinchu_fusion.py CHANGED
@@ -128,6 +128,11 @@ ORGANS = {
128
  # doctrine-intended path /api/amaru/chakra/tick is GET-only live; this is
129
  # the operational POST that yields the 13-axis Λ.
130
  "tick_path": "/api/amaru/v1/cortex/with-rosie", "health_path": "/healthz",
 
 
 
 
 
131
  },
132
  "a11oy": {
133
  "label": "Policy Officer", "quechua": "a11oy", "icon": "📜",
@@ -659,25 +664,62 @@ def register(app, space: str = "killinchu") -> dict[str, Any]:
659
  return JSONResponse({"status": "degraded", "reason": "httpx unavailable"}, status_code=503)
660
  async with httpx.AsyncClient() as client:
661
  async def _probe(name: str, cfg: dict):
 
662
  if cfg["base"] == "local":
663
- return name, {"status": "ok", "local": True}
664
  url = cfg["base"] + cfg.get("health_path", "/healthz")
 
 
 
665
  t0 = time.monotonic()
666
  try:
667
- r = await client.get(url, timeout=ORGAN_TIMEOUT_S)
668
  return name, {"status": "ok" if r.status_code == 200 else "amber",
669
- "http": r.status_code, "latency_ms": round((time.monotonic()-t0)*1000, 1)}
 
670
  except Exception as e:
671
  return name, {"status": "red", "error": f"{type(e).__name__}",
 
672
  "latency_ms": round((time.monotonic()-t0)*1000, 1)}
673
  organ_health = dict(await asyncio.gather(*[_probe(n, c) for n, c in ORGANS.items()]))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
674
  all_ok = all(v.get("status") == "ok" for v in organ_health.values())
 
 
 
 
 
 
 
 
675
  return JSONResponse({
676
- "status": "ok" if all_ok else "degraded", "doctrine": _doctrine_string(),
677
  "doctrine_numbers": {"declarations": 749, "axioms": 14, "sorries": 163}, "lean_sha": LEAN_SHA,
678
  "lambda_status": "Conjecture 1 (NOT a theorem)", "slsa": "L1 (honest)",
679
  "signing_available": (_dsse.signing_available() if _dsse else False),
680
  "keyid": getattr(_dsse, "KEYID", "szlholdings-cosign"), "organs": organ_health,
 
 
 
 
 
 
 
 
 
681
  "notice": DEFENSE_UNICORNS_NOTICE})
682
  registered.append(f"{base}/healthz")
683
 
 
128
  # doctrine-intended path /api/amaru/chakra/tick is GET-only live; this is
129
  # the operational POST that yields the 13-axis Λ.
130
  "tick_path": "/api/amaru/v1/cortex/with-rosie", "health_path": "/healthz",
131
+ # amaru is NON-ESSENTIAL: its Space is being decommissioned. Consensus must
132
+ # NOT hard-depend on it. We probe it with a short timeout and exclude it from
133
+ # the quorum-essential set so killinchu degrades gracefully when it is gone.
134
+ "essential": False,
135
+ "probe_timeout_s": float(os.environ.get("KILLINCHU_AMARU_TIMEOUT", "2.0")),
136
  },
137
  "a11oy": {
138
  "label": "Policy Officer", "quechua": "a11oy", "icon": "📜",
 
664
  return JSONResponse({"status": "degraded", "reason": "httpx unavailable"}, status_code=503)
665
  async with httpx.AsyncClient() as client:
666
  async def _probe(name: str, cfg: dict):
667
+ essential = cfg.get("essential", True)
668
  if cfg["base"] == "local":
669
+ return name, {"status": "ok", "local": True, "essential": essential}
670
  url = cfg["base"] + cfg.get("health_path", "/healthz")
671
+ # Non-essential organs (e.g. amaru, being decommissioned) get a short
672
+ # timeout so they never block the consensus health check.
673
+ timeout = cfg.get("probe_timeout_s", ORGAN_TIMEOUT_S)
674
  t0 = time.monotonic()
675
  try:
676
+ r = await client.get(url, timeout=timeout)
677
  return name, {"status": "ok" if r.status_code == 200 else "amber",
678
+ "http": r.status_code, "essential": essential,
679
+ "latency_ms": round((time.monotonic()-t0)*1000, 1)}
680
  except Exception as e:
681
  return name, {"status": "red", "error": f"{type(e).__name__}",
682
+ "essential": essential,
683
  "latency_ms": round((time.monotonic()-t0)*1000, 1)}
684
  organ_health = dict(await asyncio.gather(*[_probe(n, c) for n, c in ORGANS.items()]))
685
+ # Consensus / quorum math. Quorum is a strict majority of the TOTAL organs and
686
+ # it must NOT depend on any single non-essential organ (amaru). We count
687
+ # healthy organs and check whether a majority is reachable without amaru.
688
+ total = len(organ_health)
689
+ quorum_needed = (total // 2) + 1 # strict majority, e.g. 3 of 4
690
+ healthy = [n for n, v in organ_health.items() if v.get("status") == "ok"]
691
+ healthy_essential = [n for n in healthy if organ_health[n].get("essential", True)]
692
+ # quorum_possible: can we form a majority from currently-healthy organs?
693
+ quorum_possible = len(healthy) >= quorum_needed
694
+ # quorum_without_amaru: would quorum still hold if amaru were fully gone?
695
+ healthy_no_amaru = [n for n in healthy if n != "amaru"]
696
+ quorum_without_amaru = len(healthy_no_amaru) >= quorum_needed
697
+ amaru_h = organ_health.get("amaru", {})
698
+ amaru_offline = amaru_h.get("status") != "ok"
699
  all_ok = all(v.get("status") == "ok" for v in organ_health.values())
700
+ # Status: healthy quorum even without amaru => "ok" (graceful). Only "degraded"
701
+ # if a true majority cannot be formed.
702
+ if quorum_without_amaru:
703
+ consensus_status = "ok"
704
+ elif quorum_possible:
705
+ consensus_status = "ok"
706
+ else:
707
+ consensus_status = "degraded"
708
  return JSONResponse({
709
+ "status": consensus_status, "all_organs_ok": all_ok, "doctrine": _doctrine_string(),
710
  "doctrine_numbers": {"declarations": 749, "axioms": 14, "sorries": 163}, "lean_sha": LEAN_SHA,
711
  "lambda_status": "Conjecture 1 (NOT a theorem)", "slsa": "L1 (honest)",
712
  "signing_available": (_dsse.signing_available() if _dsse else False),
713
  "keyid": getattr(_dsse, "KEYID", "szlholdings-cosign"), "organs": organ_health,
714
+ "quorum": {
715
+ "total": total, "needed": quorum_needed,
716
+ "healthy": len(healthy), "healthy_organs": healthy,
717
+ "quorum_possible": quorum_possible,
718
+ "quorum_without_amaru": quorum_without_amaru,
719
+ "amaru_offline": amaru_offline,
720
+ "amaru_essential": False,
721
+ },
722
+ "quorum_possible": quorum_possible,
723
  "notice": DEFENSE_UNICORNS_NOTICE})
724
  registered.append(f"{base}/healthz")
725
 
serve.py CHANGED
@@ -1676,6 +1676,25 @@ except Exception as _elite_e:
1676
  _elite_tb.print_exc()
1677
  # ── end ELITE ────────────────────────────────────────────────────────────────
1678
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1679
 
1680
  # ===========================================================================
1681
  # ADDITIVE: killinchu "Beyond-Cannonico" proof console — the autonomy-governance
 
1676
  _elite_tb.print_exc()
1677
  # ── end ELITE ────────────────────────────────────────────────────────────────
1678
 
1679
+ # ===========================================================================
1680
+ # ADDITIVE: FLEET (Vessels) commercial-fleet surface (GAP-1 + GAP-2).
1681
+ # Serves the REAL platform seed-data/vessels/* datasets VERBATIM as static JSON
1682
+ # endpoints under /api/killinchu/v1/fleet/*, plus the vessels-vertical "Voyage
1683
+ # Risk Exchange" governed-decision loop (signals->forecast->evidence->recommendation
1684
+ # ->brief), ported verbatim as pure functions. Datasets are clearly-labelled SAMPLE
1685
+ # data, NOT a live AIS / class-society feed. Mounted BEFORE the SPA catch-all.
1686
+ # Doctrine v11 LOCKED · Λ = Conjecture 1 · SLSA L2. NO mocks.
1687
+ # ===========================================================================
1688
+ try:
1689
+ import killinchu_fleet_vessels as _fleet
1690
+ _fleet_status = _fleet.register(app)
1691
+ print(f"[killinchu] FLEET vessels registered: {_fleet_status.get('registered_count')} routes", file=sys.stderr)
1692
+ except Exception as _fleet_e:
1693
+ import traceback as _fleet_tb
1694
+ print(f"[killinchu] FLEET vessels NOT registered: {_fleet_e!r}", file=sys.stderr)
1695
+ _fleet_tb.print_exc()
1696
+ # ── end FLEET ────────────────────────────────────────────────────────────────
1697
+
1698
 
1699
  # ===========================================================================
1700
  # ADDITIVE: killinchu "Beyond-Cannonico" proof console — the autonomy-governance