gini-toolkit 6.0.1.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gini/__init__.py +12 -0
- gini/__main__.py +107 -0
- gini/_version.py +24 -0
- gini/agent/__init__.py +17 -0
- gini/agent/agent_gamemaster.py +140 -0
- gini/agent/api.py +291 -0
- gini/agent/ask.py +123 -0
- gini/agent/authoring.py +72 -0
- gini/agent/blackboard.py +114 -0
- gini/agent/contracts.py +142 -0
- gini/agent/domains.py +91 -0
- gini/agent/embed.py +123 -0
- gini/agent/gamemaster.py +256 -0
- gini/agent/kb.py +148 -0
- gini/agent/lesson_resolver.py +261 -0
- gini/agent/llm/__init__.py +5 -0
- gini/agent/llm/backend.py +43 -0
- gini/agent/llm/fake.py +25 -0
- gini/agent/llm/ollama.py +206 -0
- gini/agent/loop.py +258 -0
- gini/agent/mcp_server.py +86 -0
- gini/agent/meaning.py +225 -0
- gini/agent/mission.py +210 -0
- gini/agent/mission_controller.py +208 -0
- gini/agent/narration.py +116 -0
- gini/agent/notifier.py +86 -0
- gini/agent/personas.py +79 -0
- gini/agent/reasoning.py +172 -0
- gini/agent/recall.py +248 -0
- gini/agent/session.py +79 -0
- gini/agent/teaching_center.py +482 -0
- gini/agent/tools/__init__.py +3 -0
- gini/agent/tools/registry.py +193 -0
- gini/agent/twin/__init__.py +28 -0
- gini/agent/twin/authoring.py +71 -0
- gini/agent/twin/contracts.py +54 -0
- gini/agent/twin/dialectic.py +189 -0
- gini/agent/twin/harness.py +93 -0
- gini/agent/twin/justify.py +156 -0
- gini/agent/twin/learner.py +64 -0
- gini/agent/twin/mission.py +60 -0
- gini/agent/twin/os_coach.py +79 -0
- gini/agent/twin/salience.py +30 -0
- gini/agent/understand.py +250 -0
- gini/agent/verifiers.py +106 -0
- gini/agent/wizard.py +178 -0
- gini/agent/xv6_pack.py +74 -0
- gini/app/__init__.py +3 -0
- gini/app/context.py +368 -0
- gini/app/paths.py +121 -0
- gini/data/README.md +21 -0
- gini/domain/__init__.py +9 -0
- gini/domain/assembly.py +209 -0
- gini/domain/authoring.py +353 -0
- gini/domain/blueprints.py +5 -0
- gini/domain/capabilities.py +177 -0
- gini/domain/catalog.py +85 -0
- gini/domain/certify.py +201 -0
- gini/domain/compose.py +413 -0
- gini/domain/composition.py +88 -0
- gini/domain/concepts.py +383 -0
- gini/domain/connection_rules.py +269 -0
- gini/domain/constraints.py +153 -0
- gini/domain/content.py +59 -0
- gini/domain/cpu_journey.py +89 -0
- gini/domain/devices.py +747 -0
- gini/domain/diagnose.py +201 -0
- gini/domain/element_guide.py +327 -0
- gini/domain/explain.py +90 -0
- gini/domain/fingerprint.py +201 -0
- gini/domain/firewall.py +34 -0
- gini/domain/flowlog.py +61 -0
- gini/domain/flowtable.py +179 -0
- gini/domain/fragment_yaml.py +230 -0
- gini/domain/fragments.py +169 -0
- gini/domain/games/__init__.py +2 -0
- gini/domain/games/paging_games.py +119 -0
- gini/domain/games/policy_game.py +86 -0
- gini/domain/games/process_game.py +48 -0
- gini/domain/games/thrash_game.py +75 -0
- gini/domain/games/translate_game.py +60 -0
- gini/domain/games/trap_game.py +86 -0
- gini/domain/grader.py +155 -0
- gini/domain/grouping.py +67 -0
- gini/domain/legality.py +103 -0
- gini/domain/lesson.py +241 -0
- gini/domain/lexicon.py +150 -0
- gini/domain/machine_state.py +410 -0
- gini/domain/missions/networking/basic-lan.yaml +32 -0
- gini/domain/missions/networking/cache-in-front.yaml +23 -0
- gini/domain/missions/networking/decouple-with-queue.yaml +31 -0
- gini/domain/missions/networking/drive-load.yaml +20 -0
- gini/domain/missions/networking/fix-the-address.yaml +75 -0
- gini/domain/missions/networking/fix-the-lan.yaml +43 -0
- gini/domain/missions/networking/inspect-flows.yaml +16 -0
- gini/domain/missions/networking/k8s-autoscale.yaml +27 -0
- gini/domain/missions/networking/least-privilege.yaml +21 -0
- gini/domain/missions/networking/load-balanced-web.yaml +29 -0
- gini/domain/missions/networking/observe-it.yaml +24 -0
- gini/domain/missions/networking/put-in-vpc.yaml +30 -0
- gini/domain/missions/networking/reachability-boundary.yaml +56 -0
- gini/domain/missions/networking/sdn-reactive.yaml +35 -0
- gini/domain/missions/networking/send-request.yaml +19 -0
- gini/domain/missions/networking/serverless-api.yaml +25 -0
- gini/domain/missions/networking/service-chain.yaml +33 -0
- gini/domain/missions/os/lottery-fix.yaml +19 -0
- gini/domain/missions/os/priority-fix.yaml +24 -0
- gini/domain/missions.py +111 -0
- gini/domain/modulechain.py +36 -0
- gini/domain/objectives.py +488 -0
- gini/domain/os_zoo.py +79 -0
- gini/domain/paging_sim.py +141 -0
- gini/domain/pricing.py +199 -0
- gini/domain/probes.py +226 -0
- gini/domain/profile.py +142 -0
- gini/domain/recipes.py +738 -0
- gini/domain/riders.py +309 -0
- gini/domain/router_modules.py +224 -0
- gini/domain/routetable.py +67 -0
- gini/domain/scoring.py +76 -0
- gini/domain/staging.py +122 -0
- gini/domain/syscall_builder.py +144 -0
- gini/domain/topic_cloud.py +62 -0
- gini/domain/topology.py +213 -0
- gini/domain/vocabulary.py +51 -0
- gini/domain/xv6.py +808 -0
- gini/domain/xv6_fs.py +250 -0
- gini/domain/xv6_runner.py +113 -0
- gini/domain/xv6_vm.py +385 -0
- gini/gloader.py +17 -0
- gini/runtime/__init__.py +18 -0
- gini/runtime/cloudfabric_agent.py +370 -0
- gini/runtime/console.py +68 -0
- gini/runtime/control.py +70 -0
- gini/runtime/frame.py +138 -0
- gini/runtime/gbridge.py +638 -0
- gini/runtime/grouter.py +223 -0
- gini/runtime/hostsim.py +90 -0
- gini/runtime/shuttle.py +348 -0
- gini/runtime/switch.py +109 -0
- gini/runtime/transport.py +77 -0
- gini/runtime/xv6_bridge.py +312 -0
- gini/server/__init__.py +22 -0
- gini/server/__main__.py +74 -0
- gini/server/app.py +140 -0
- gini/server/auth.py +82 -0
- gini/server/policy.py +57 -0
- gini/server/session.py +23 -0
- gini/services/__init__.py +15 -0
- gini/services/boardflash.py +248 -0
- gini/services/boardsetup.py +374 -0
- gini/services/cloud_catalog.py +143 -0
- gini/services/compiler.py +1858 -0
- gini/services/discovery.py +324 -0
- gini/services/gloader.py +183 -0
- gini/services/orchestrator.py +1460 -0
- gini/services/persistence.py +28 -0
- gini/services/probe_runner.py +149 -0
- gini/services/project.py +217 -0
- gini/services/remote.py +93 -0
- gini/services/rider_runner.py +96 -0
- gini/services/rider_session.py +171 -0
- gini/services/shadow_store.py +52 -0
- gini/services/terminal.py +45 -0
- gini/setup/__init__.py +17 -0
- gini/setup/cli.py +109 -0
- gini/setup/images.py +33 -0
- gini/setup/marker.py +43 -0
- gini/setup/runtime.py +69 -0
- gini/ui/__init__.py +3 -0
- gini/ui/assets/app_icon.icns +0 -0
- gini/ui/assets/app_icon.ico +0 -0
- gini/ui/assets/app_icon.png +0 -0
- gini/ui/assets/app_icon_1024.png +0 -0
- gini/ui/assets/cue/_w.txt +1 -0
- gini/ui/assets/cue/ai.png +0 -0
- gini/ui/assets/cue/canvas.png +0 -0
- gini/ui/assets/cue/cloud.png +0 -0
- gini/ui/assets/cue/cost.png +0 -0
- gini/ui/assets/cue/dark/ai.png +0 -0
- gini/ui/assets/cue/dark/canvas.png +0 -0
- gini/ui/assets/cue/dark/cloud.png +0 -0
- gini/ui/assets/cue/dark/cost.png +0 -0
- gini/ui/assets/cue/dark/metrics.png +0 -0
- gini/ui/assets/cue/dark/router.png +0 -0
- gini/ui/assets/cue/dark/run.png +0 -0
- gini/ui/assets/cue/dark/serverless.png +0 -0
- gini/ui/assets/cue/dark/settings.png +0 -0
- gini/ui/assets/cue/dark/welcome.png +0 -0
- gini/ui/assets/cue/dark/wizard.png +0 -0
- gini/ui/assets/cue/ginibrand/ai.png +0 -0
- gini/ui/assets/cue/ginibrand/canvas.png +0 -0
- gini/ui/assets/cue/ginibrand/cloud.png +0 -0
- gini/ui/assets/cue/ginibrand/cost.png +0 -0
- gini/ui/assets/cue/ginibrand/metrics.png +0 -0
- gini/ui/assets/cue/ginibrand/router.png +0 -0
- gini/ui/assets/cue/ginibrand/run.png +0 -0
- gini/ui/assets/cue/ginibrand/serverless.png +0 -0
- gini/ui/assets/cue/ginibrand/settings.png +0 -0
- gini/ui/assets/cue/ginibrand/welcome.png +0 -0
- gini/ui/assets/cue/ginibrand/wizard.png +0 -0
- gini/ui/assets/cue/highcontrast/ai.png +0 -0
- gini/ui/assets/cue/highcontrast/canvas.png +0 -0
- gini/ui/assets/cue/highcontrast/cloud.png +0 -0
- gini/ui/assets/cue/highcontrast/cost.png +0 -0
- gini/ui/assets/cue/highcontrast/metrics.png +0 -0
- gini/ui/assets/cue/highcontrast/router.png +0 -0
- gini/ui/assets/cue/highcontrast/run.png +0 -0
- gini/ui/assets/cue/highcontrast/serverless.png +0 -0
- gini/ui/assets/cue/highcontrast/settings.png +0 -0
- gini/ui/assets/cue/highcontrast/welcome.png +0 -0
- gini/ui/assets/cue/highcontrast/wizard.png +0 -0
- gini/ui/assets/cue/light/ai.png +0 -0
- gini/ui/assets/cue/light/canvas.png +0 -0
- gini/ui/assets/cue/light/cloud.png +0 -0
- gini/ui/assets/cue/light/cost.png +0 -0
- gini/ui/assets/cue/light/metrics.png +0 -0
- gini/ui/assets/cue/light/router.png +0 -0
- gini/ui/assets/cue/light/run.png +0 -0
- gini/ui/assets/cue/light/serverless.png +0 -0
- gini/ui/assets/cue/light/settings.png +0 -0
- gini/ui/assets/cue/light/welcome.png +0 -0
- gini/ui/assets/cue/light/wizard.png +0 -0
- gini/ui/assets/cue/metrics.png +0 -0
- gini/ui/assets/cue/router.png +0 -0
- gini/ui/assets/cue/run.png +0 -0
- gini/ui/assets/cue/serverless.png +0 -0
- gini/ui/assets/cue/settings.png +0 -0
- gini/ui/assets/cue/welcome.png +0 -0
- gini/ui/assets/cue/wizard.png +0 -0
- gini/ui/assistant.py +2111 -0
- gini/ui/author_dialog.py +184 -0
- gini/ui/board_dialog.py +247 -0
- gini/ui/branding.py +21 -0
- gini/ui/canvas.py +2007 -0
- gini/ui/chat_panel.py +7 -0
- gini/ui/cpu_journey.py +212 -0
- gini/ui/cpu_lab.py +306 -0
- gini/ui/cue_cards.py +214 -0
- gini/ui/dashboard.py +222 -0
- gini/ui/diagnose_game.py +336 -0
- gini/ui/fingerprint_lab.py +219 -0
- gini/ui/flash_dialog.py +244 -0
- gini/ui/flow_layout.py +63 -0
- gini/ui/fragment_manager.py +1415 -0
- gini/ui/game_catalog.py +184 -0
- gini/ui/game_renderers.py +340 -0
- gini/ui/games_lab.py +90 -0
- gini/ui/inspector.py +1055 -0
- gini/ui/live_metrics.py +130 -0
- gini/ui/machine_lab.py +1412 -0
- gini/ui/main_window.py +3153 -0
- gini/ui/memory_lab.py +371 -0
- gini/ui/mission_panel.py +302 -0
- gini/ui/mode_indicator.py +227 -0
- gini/ui/palette.py +112 -0
- gini/ui/peripherals.py +218 -0
- gini/ui/process_tree.py +130 -0
- gini/ui/reset_dialog.py +179 -0
- gini/ui/router_lab.py +776 -0
- gini/ui/run_button.py +183 -0
- gini/ui/settings_dialog.py +234 -0
- gini/ui/signin_dialog.py +111 -0
- gini/ui/storage_lab.py +219 -0
- gini/ui/syscall_builder.py +235 -0
- gini/ui/syscall_lab.py +152 -0
- gini/ui/theme/__init__.py +5 -0
- gini/ui/theme/icons.py +145 -0
- gini/ui/theme/manager.py +291 -0
- gini/ui/theme/tokens.py +194 -0
- gini/ui/trap_lab.py +270 -0
- gini/ui/worker_host.py +102 -0
- gini/ui/zoo_lab.py +112 -0
- gini_toolkit-6.0.1.dev0.dist-info/METADATA +77 -0
- gini_toolkit-6.0.1.dev0.dist-info/RECORD +278 -0
- gini_toolkit-6.0.1.dev0.dist-info/WHEEL +5 -0
- gini_toolkit-6.0.1.dev0.dist-info/entry_points.txt +3 -0
- gini_toolkit-6.0.1.dev0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
id: least-privilege
|
|
2
|
+
layer: core
|
|
3
|
+
teaches: security-groups
|
|
4
|
+
spirit: a workload is protected by a default-deny security group that opens only the ports it needs —
|
|
5
|
+
least privilege by design.
|
|
6
|
+
summary: Protect a workload with a security group.
|
|
7
|
+
parent: security
|
|
8
|
+
provides:
|
|
9
|
+
- micro-segmentation
|
|
10
|
+
- web-endpoint
|
|
11
|
+
objectives:
|
|
12
|
+
- id: has-sg
|
|
13
|
+
say: Place a security group
|
|
14
|
+
check: exists(security_group)
|
|
15
|
+
- id: has-workload
|
|
16
|
+
say: Place a workload to protect
|
|
17
|
+
check: exists(web_app) or exists(database) or exists(instance)
|
|
18
|
+
misconceptions:
|
|
19
|
+
- Leaving a datastore open to the world instead of scoping ingress.
|
|
20
|
+
peers:
|
|
21
|
+
- service-chain
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
id: load-balanced-web
|
|
2
|
+
layer: core
|
|
3
|
+
teaches: load-balancing
|
|
4
|
+
spirit: incoming traffic is spread across several backend replicas via a load balancer.
|
|
5
|
+
summary: Front two or more web replicas with a load balancer.
|
|
6
|
+
parent: cloud/scaling
|
|
7
|
+
provides:
|
|
8
|
+
- load-distributor
|
|
9
|
+
- public-entrypoint
|
|
10
|
+
- web-endpoint
|
|
11
|
+
- metrics-source
|
|
12
|
+
objectives:
|
|
13
|
+
- id: has-lb
|
|
14
|
+
say: Place a load balancer
|
|
15
|
+
check: exists(load_balancer)
|
|
16
|
+
- id: enough-backs
|
|
17
|
+
say: Place at least two web backends
|
|
18
|
+
check: count(web_app) >= 2
|
|
19
|
+
- id: fans-out
|
|
20
|
+
say: Wire the load balancer to the backends
|
|
21
|
+
check: path(load_balancer, web_app)
|
|
22
|
+
- id: live-balances
|
|
23
|
+
say: 'Live: the load balancer really has 2+ live backends behind it (Run to check)'
|
|
24
|
+
kind: behavioral
|
|
25
|
+
probe: balances(load_balancer, >= 2)
|
|
26
|
+
misconceptions:
|
|
27
|
+
- A single backend behind the LB (nothing to balance).
|
|
28
|
+
peers:
|
|
29
|
+
- k8s-autoscale
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
id: observe-it
|
|
2
|
+
layer: observe
|
|
3
|
+
teaches: observability
|
|
4
|
+
spirit: a dashboard is fed by a metrics source so the system can be watched — the dashboard must connect
|
|
5
|
+
to a metrics collector.
|
|
6
|
+
summary: Wire a dashboard to a metrics source.
|
|
7
|
+
parent: observability
|
|
8
|
+
provides:
|
|
9
|
+
- visualizer
|
|
10
|
+
- dashboard-view
|
|
11
|
+
- metrics-collector
|
|
12
|
+
- metrics-source
|
|
13
|
+
objectives:
|
|
14
|
+
- id: has-metrics
|
|
15
|
+
say: Place a metrics source
|
|
16
|
+
check: exists(metrics)
|
|
17
|
+
- id: has-dash
|
|
18
|
+
say: Place a dashboard
|
|
19
|
+
check: exists(dashboard)
|
|
20
|
+
- id: wired
|
|
21
|
+
say: Wire the dashboard to the metrics source
|
|
22
|
+
check: path(dashboard, metrics)
|
|
23
|
+
misconceptions:
|
|
24
|
+
- A dashboard with no metrics source has nothing to show.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
id: put-in-vpc
|
|
2
|
+
layer: core
|
|
3
|
+
teaches: vpc-networking
|
|
4
|
+
spirit: the workloads sit inside the VPC boundary — however the student draws the grouping.
|
|
5
|
+
summary: Place a web app and a database inside a VPC.
|
|
6
|
+
parent: cloud/vpc
|
|
7
|
+
provides:
|
|
8
|
+
- vpc-boundary
|
|
9
|
+
- web-endpoint
|
|
10
|
+
- relational-store
|
|
11
|
+
objectives:
|
|
12
|
+
- id: has-vpc
|
|
13
|
+
say: Place a VPC on the canvas
|
|
14
|
+
check: exists(vpc)
|
|
15
|
+
- id: has-web
|
|
16
|
+
say: Place a web app
|
|
17
|
+
check: exists(web_app)
|
|
18
|
+
- id: has-db
|
|
19
|
+
say: Place a managed database
|
|
20
|
+
check: exists(database)
|
|
21
|
+
- id: web-in
|
|
22
|
+
say: The web app sits inside the VPC
|
|
23
|
+
check: contains_type(vpc, web_app)
|
|
24
|
+
- id: db-in
|
|
25
|
+
say: The database sits inside the VPC
|
|
26
|
+
check: contains_type(vpc, database)
|
|
27
|
+
misconceptions:
|
|
28
|
+
- Leaving a workload outside the box but wired in — containment is membership.
|
|
29
|
+
peers:
|
|
30
|
+
- reachability-boundary
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
id: reachability-boundary
|
|
2
|
+
layer: core
|
|
3
|
+
teaches: vpc-networking
|
|
4
|
+
spirit: a database is reachable by the web tier but NOT from the Internet — by a private subnet, a security
|
|
5
|
+
group, or firewall placement; the mechanism is free.
|
|
6
|
+
summary: Keep a database reachable from the web tier but hidden from the Internet.
|
|
7
|
+
parent: cloud/vpc
|
|
8
|
+
provides:
|
|
9
|
+
- vpc-boundary
|
|
10
|
+
- web-endpoint
|
|
11
|
+
- relational-store
|
|
12
|
+
# Objectives form a PROGRESSIVE LADDER (the engine orders them by tier automatically):
|
|
13
|
+
# L1 placement -> L2 connection -> L3 containment -> L4 live.
|
|
14
|
+
# Independent tasks come first, so a beginner always has an obvious next move. Later tasks can be
|
|
15
|
+
# satisfied incidentally by earlier ones (dropping the web app INSIDE the VPC ticks both L1 and L3).
|
|
16
|
+
objectives:
|
|
17
|
+
# --- L1: place the elements -------------------------------------------------
|
|
18
|
+
- id: has-vpc
|
|
19
|
+
say: Place a VPC on the canvas
|
|
20
|
+
check: exists(vpc)
|
|
21
|
+
- id: has-web
|
|
22
|
+
say: Place a web app
|
|
23
|
+
check: exists(web_app)
|
|
24
|
+
- id: has-db
|
|
25
|
+
say: Place a managed database
|
|
26
|
+
check: exists(database)
|
|
27
|
+
# --- L2: make the connection ------------------------------------------------
|
|
28
|
+
- id: web-to-db
|
|
29
|
+
say: Connect the web app to the database
|
|
30
|
+
check: path(web_app, database)
|
|
31
|
+
# --- L3: group them correctly (containment) ---------------------------------
|
|
32
|
+
- id: web-in-vpc
|
|
33
|
+
say: The web app sits inside the VPC
|
|
34
|
+
check: contains_type(vpc, web_app)
|
|
35
|
+
- id: db-in-vpc
|
|
36
|
+
say: The database sits inside the VPC
|
|
37
|
+
check: contains_type(vpc, database)
|
|
38
|
+
- id: shielded
|
|
39
|
+
say: The database is not reachable from the Internet
|
|
40
|
+
# guard the negative with existence, or it passes VACUOUSLY on an empty board
|
|
41
|
+
check: exists(database) and not path(cloud, database)
|
|
42
|
+
level: 3 # isolation is an ADVANCED idea, even though its predicate reads as a connection
|
|
43
|
+
# --- L4: prove it live (needs the running system) ----------------------------
|
|
44
|
+
- id: live-reach
|
|
45
|
+
say: 'Live: the web app actually reaches the database (Run to check)'
|
|
46
|
+
kind: behavioral
|
|
47
|
+
probe: reach(web_app -> database) == ok
|
|
48
|
+
- id: live-shield
|
|
49
|
+
say: 'Live: the database refuses the Internet (Run to check)'
|
|
50
|
+
kind: behavioral
|
|
51
|
+
probe: reach(cloud -> database) == fail
|
|
52
|
+
misconceptions:
|
|
53
|
+
- Thinking 'private' means unreachable by everything (still reachable inside).
|
|
54
|
+
- Exposing the DB to the Internet to 'make it work'.
|
|
55
|
+
peers:
|
|
56
|
+
- put-in-vpc
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
id: sdn-reactive
|
|
2
|
+
layer: core
|
|
3
|
+
teaches: sdn
|
|
4
|
+
spirit: an OpenFlow switch has no built-in logic — it MUST be wired to a controller for traffic to flow;
|
|
5
|
+
hosts connect to the switch.
|
|
6
|
+
summary: Wire an OpenVSwitch to a controller, with hosts on the switch.
|
|
7
|
+
parent: sdn
|
|
8
|
+
provides:
|
|
9
|
+
- sdn-fabric
|
|
10
|
+
- flow-control
|
|
11
|
+
- service-endpoint
|
|
12
|
+
objectives:
|
|
13
|
+
- id: has-ovs
|
|
14
|
+
say: Place an OpenVSwitch
|
|
15
|
+
check: exists(ovs)
|
|
16
|
+
- id: has-ctrl
|
|
17
|
+
say: Place a controller
|
|
18
|
+
check: exists(controller)
|
|
19
|
+
- id: has-hosts
|
|
20
|
+
say: Place at least two hosts
|
|
21
|
+
check: count(host) >= 2
|
|
22
|
+
- id: ovs-controlled
|
|
23
|
+
say: Wire the switch to the controller
|
|
24
|
+
check: link(ovs, controller)
|
|
25
|
+
- id: hosts-on-ovs
|
|
26
|
+
say: Wire the hosts to the switch
|
|
27
|
+
check: link(host, ovs)
|
|
28
|
+
- id: live-flows
|
|
29
|
+
say: 'Live: the controller actually installed flows on the switch (Run to check)'
|
|
30
|
+
kind: behavioral
|
|
31
|
+
probe: flow_installed(ovs, any)
|
|
32
|
+
misconceptions:
|
|
33
|
+
- Expecting an OVS to forward with no controller attached (it can't).
|
|
34
|
+
peers:
|
|
35
|
+
- basic-lan
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
id: send-request
|
|
2
|
+
layer: exercise
|
|
3
|
+
teaches: request-path
|
|
4
|
+
spirit: the student sends a single request through the system and follows its path end to end — proving
|
|
5
|
+
the wiring actually carries traffic.
|
|
6
|
+
summary: Send a request through the system and trace its path.
|
|
7
|
+
parent: traffic
|
|
8
|
+
step: Add a host, send a request toward the service, and follow the path it takes.
|
|
9
|
+
provides:
|
|
10
|
+
- request-client
|
|
11
|
+
requires:
|
|
12
|
+
- traffic-sink
|
|
13
|
+
objectives:
|
|
14
|
+
- id: has-caller
|
|
15
|
+
say: There is a client to send a request
|
|
16
|
+
check: exists(host)
|
|
17
|
+
misconceptions:
|
|
18
|
+
- Assuming a drawn link works without ever sending traffic over it.
|
|
19
|
+
catalog: false
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
id: serverless-api
|
|
2
|
+
layer: core
|
|
3
|
+
teaches: serverless
|
|
4
|
+
spirit: an API gateway fronts a function so an HTTP request to the gateway invokes it — the gateway must
|
|
5
|
+
route to the function.
|
|
6
|
+
summary: Put a function behind an API gateway.
|
|
7
|
+
parent: serverless
|
|
8
|
+
provides:
|
|
9
|
+
- serverless-fn
|
|
10
|
+
- api-endpoint
|
|
11
|
+
- public-entrypoint
|
|
12
|
+
objectives:
|
|
13
|
+
- id: has-fn
|
|
14
|
+
say: Place a function
|
|
15
|
+
check: exists(function)
|
|
16
|
+
- id: has-gw
|
|
17
|
+
say: Place an API gateway
|
|
18
|
+
check: exists(api_gateway)
|
|
19
|
+
- id: routed
|
|
20
|
+
say: Route the gateway to the function
|
|
21
|
+
check: path(api_gateway, function)
|
|
22
|
+
misconceptions:
|
|
23
|
+
- A function with no gateway — nothing can invoke it over HTTP.
|
|
24
|
+
peers:
|
|
25
|
+
- decouple-with-queue
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
id: service-chain
|
|
2
|
+
layer: core
|
|
3
|
+
teaches: sfc
|
|
4
|
+
spirit: traffic passes THROUGH a network function on its way out — a firewall sits in the path between
|
|
5
|
+
the host and the Internet, by any valid wiring.
|
|
6
|
+
summary: Steer a host's traffic through a firewall to the Internet.
|
|
7
|
+
parent: nfv/sfc
|
|
8
|
+
provides:
|
|
9
|
+
- inline-nf
|
|
10
|
+
- perimeter-filter
|
|
11
|
+
- internet-egress
|
|
12
|
+
- service-endpoint
|
|
13
|
+
objectives:
|
|
14
|
+
- id: has-fw
|
|
15
|
+
say: Place a firewall
|
|
16
|
+
check: exists(firewall)
|
|
17
|
+
- id: has-host
|
|
18
|
+
say: Place a host
|
|
19
|
+
check: exists(host)
|
|
20
|
+
- id: has-net
|
|
21
|
+
say: Place an Internet element
|
|
22
|
+
check: exists(cloud)
|
|
23
|
+
- id: host-reaches-fw
|
|
24
|
+
say: The host's traffic reaches the firewall
|
|
25
|
+
check: path(host, firewall)
|
|
26
|
+
- id: through-fw
|
|
27
|
+
say: Traffic to the Internet passes through the firewall (no bypass)
|
|
28
|
+
check: through(firewall, host, cloud)
|
|
29
|
+
level: 3
|
|
30
|
+
misconceptions:
|
|
31
|
+
- Wiring the host straight to the Internet, bypassing the function.
|
|
32
|
+
peers:
|
|
33
|
+
- least-privilege
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
id: xv6-lottery-fix
|
|
2
|
+
layer: core
|
|
3
|
+
teaches: os-scheduling
|
|
4
|
+
spirit: the shipped lottery scheduler ignores tickets (it picks uniformly at random); a correct
|
|
5
|
+
shadow makes each process's CPU share track its ticket count.
|
|
6
|
+
summary: Make lottery scheduling give CPU share proportional to tickets.
|
|
7
|
+
parent: os-scheduling
|
|
8
|
+
objectives:
|
|
9
|
+
- id: shadow-wired
|
|
10
|
+
say: Your lottery_sched shadow is running
|
|
11
|
+
kind: behavioral
|
|
12
|
+
probe: measure(shadow_active, lottery_sched) == 1
|
|
13
|
+
- id: share-tracks-tickets
|
|
14
|
+
say: CPU share matches the ticket ratio
|
|
15
|
+
kind: behavioral
|
|
16
|
+
probe: measure(share_ratio, tickets) <= 0.15
|
|
17
|
+
misconceptions:
|
|
18
|
+
- A lottery that ignores tickets is just randomized round-robin.
|
|
19
|
+
- Equal ticket counts should give equal share; unequal counts should not.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
id: xv6-priority-fix
|
|
2
|
+
layer: core
|
|
3
|
+
teaches: os-scheduling
|
|
4
|
+
spirit: the shipped priority scheduler starves low-priority processes (it favours the lowest pid on
|
|
5
|
+
ties and ages too weakly); a correct shadow keeps the highest-priority process served AND ages
|
|
6
|
+
waiting processes so none starves forever.
|
|
7
|
+
summary: Fix the priority scheduler so high priority wins without starving anyone.
|
|
8
|
+
parent: os-scheduling
|
|
9
|
+
objectives:
|
|
10
|
+
- id: shadow-wired
|
|
11
|
+
say: Your prio_sched shadow is running
|
|
12
|
+
kind: behavioral
|
|
13
|
+
probe: measure(shadow_active, prio_sched) == 1
|
|
14
|
+
- id: high-priority-served
|
|
15
|
+
say: The highest-priority process gets its share of the CPU
|
|
16
|
+
kind: behavioral
|
|
17
|
+
probe: measure(cpu_share, highest_priority) >= 0.40
|
|
18
|
+
- id: no-starvation
|
|
19
|
+
say: No runnable process is starved
|
|
20
|
+
kind: behavioral
|
|
21
|
+
probe: measure(max_wait_slices, any) <= 8
|
|
22
|
+
misconceptions:
|
|
23
|
+
- Strict priority with no aging starves low-priority processes forever.
|
|
24
|
+
- Breaking ties by pid makes the lowest pid look higher-priority than it is.
|
gini/domain/missions.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""Missions — the Wizard's objective layer.
|
|
2
|
+
|
|
3
|
+
The Wizard is "X-ray with a goal." A student states an objective in plain words ("a
|
|
4
|
+
multi-LAN IP network"); that becomes a **Mission**: the set of element types relevant to
|
|
5
|
+
the goal, plus a suggested first element. The canvas then filters X-ray to on-goal
|
|
6
|
+
neighbours, dims off-goal palette items, and flags off-goal drops.
|
|
7
|
+
|
|
8
|
+
This module is the deterministic, offline base (keyword → relevant types). When a local
|
|
9
|
+
LLM is connected the assistant refines the type set and restates the goal, but everything
|
|
10
|
+
works without a model — just more coarsely.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import re
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class Mission:
|
|
20
|
+
goal: str
|
|
21
|
+
types: frozenset[str] # element type_keys that belong to this objective
|
|
22
|
+
first: str | None = None # a good first element to drop
|
|
23
|
+
|
|
24
|
+
def allows(self, type_key: str) -> bool:
|
|
25
|
+
return type_key in self.types
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# category -> (relevant element type_keys, anchor/first element, trigger keywords)
|
|
29
|
+
_CATS: tuple[tuple[str, set[str], str, tuple[str, ...]], ...] = (
|
|
30
|
+
("net", {"router", "switch", "hub", "host", "firewall", "wap", "cloud"}, "router",
|
|
31
|
+
("lan", "subnet", "router", "switch", "ethernet", "ip network", "ping", "route",
|
|
32
|
+
"vlan", "gateway", "multi-lan", "broadcast", "network topology", "campus")),
|
|
33
|
+
("sdn", {"ovs", "controller", "host", "switch", "cloud"}, "controller",
|
|
34
|
+
("sdn", "openflow", "flow table", "ovs", "openvswitch", "software-defined",
|
|
35
|
+
"software defined", "controller")),
|
|
36
|
+
("k8s", {"k8s_cluster", "pod", "instance_group", "registry", "load_balancer",
|
|
37
|
+
"metrics", "dashboard", "load_generator"}, "k8s_cluster",
|
|
38
|
+
("kubernetes", "k8s", "pod", "orchestrat", "deployment", "autoscal", "hpa",
|
|
39
|
+
"replica", "k3s")),
|
|
40
|
+
("cloudnet", {"vpc", "cloud_subnet", "security_group", "gateway", "region",
|
|
41
|
+
"instance", "web_app", "container"}, "vpc",
|
|
42
|
+
("vpc", "security group", "isolat", "private network", "availability zone",
|
|
43
|
+
"subnet within", "tenancy")),
|
|
44
|
+
("web", {"web_app", "instance", "container", "load_balancer", "proxy", "gateway",
|
|
45
|
+
"database", "nosql", "cache", "metrics", "dashboard", "load_generator",
|
|
46
|
+
"registry"}, "web_app",
|
|
47
|
+
("web", "http", "microservice", "api", "backend", "frontend", "website", "web app",
|
|
48
|
+
"web service", "three-tier", "3-tier", "scal", "load balanc")),
|
|
49
|
+
("serverless", {"function", "api_gateway", "queue", "object_store", "database"},
|
|
50
|
+
"function",
|
|
51
|
+
("serverless", "function", "faas", "lambda", "event-driven", "pub/sub", "messaging",
|
|
52
|
+
"queue")),
|
|
53
|
+
("data", {"database", "nosql", "cache", "object_store", "block_volume"}, "database",
|
|
54
|
+
("database", "sql", "postgres", "mongo", "cache", "redis", "object storage",
|
|
55
|
+
"bucket", "datastore", "persist")),
|
|
56
|
+
("obs", {"metrics", "dashboard", "tracing"}, "metrics",
|
|
57
|
+
("monitor", "observab", "metric", "dashboard", "prometheus", "grafana", "trace",
|
|
58
|
+
"telemetry")),
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _palette_keys() -> set[str]:
|
|
63
|
+
from .devices import by_category
|
|
64
|
+
return {d.key for items in by_category().values() for d in items}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# first-element preference when several categories match (most "anchoring" element wins)
|
|
68
|
+
_ANCHOR_PRIORITY = ("k8s_cluster", "controller", "router", "vpc", "web_app", "function",
|
|
69
|
+
"database", "metrics", "instance")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _hit(keyword: str, goal: str) -> bool:
|
|
73
|
+
"""Match a keyword on a word boundary (a leading \\b, prefixes allowed) so 'wan' no
|
|
74
|
+
longer matches 'want' and 'route' still matches 'router'."""
|
|
75
|
+
return re.search(r"\b" + re.escape(keyword), goal) is not None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def keyword_mission(goal: str) -> Mission:
|
|
79
|
+
"""Deterministic objective → relevant element types (the offline base the LLM refines).
|
|
80
|
+
Unrecognized goals impose no constraint (every palette element is on-goal)."""
|
|
81
|
+
g = (goal or "").lower()
|
|
82
|
+
types: set[str] = set()
|
|
83
|
+
first: str | None = None
|
|
84
|
+
for _key, tset, anchor, kws in _CATS:
|
|
85
|
+
if any(_hit(kw, g) for kw in kws):
|
|
86
|
+
types |= tset
|
|
87
|
+
if first is None:
|
|
88
|
+
first = anchor
|
|
89
|
+
if not types: # nothing recognized -> don't constrain
|
|
90
|
+
return Mission(goal.strip(), frozenset(_palette_keys()), None)
|
|
91
|
+
return Mission(goal.strip(), frozenset(types), anchor_for(types) or first)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def anchor_for(types) -> str | None:
|
|
95
|
+
"""The best 'first element to drop' for a set of relevant types."""
|
|
96
|
+
for k in _ANCHOR_PRIORITY:
|
|
97
|
+
if k in types:
|
|
98
|
+
return k
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def refine_types(raw: str) -> set[str]:
|
|
103
|
+
"""Pull element type_keys from free LLM text (a lenient membership scan over the
|
|
104
|
+
registry's keys and labels) — used to refine a mission when a model is connected."""
|
|
105
|
+
from .devices import REGISTRY
|
|
106
|
+
low = (raw or "").lower()
|
|
107
|
+
hits: set[str] = set()
|
|
108
|
+
for key, dt in REGISTRY.items():
|
|
109
|
+
if key in low or f" {dt.label.lower()}" in f" {low}":
|
|
110
|
+
hits.add(key)
|
|
111
|
+
return hits
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Parse the gRouter's LIVE inline module chain — the deployed Service Function Chain.
|
|
2
|
+
|
|
3
|
+
The gRouter's data-plane pipeline is editable/inspectable over the rctl socket via `gpipe`
|
|
4
|
+
(`element_query(router, "gpipe list")`). Its `list` output (gr_control.c) looks like:
|
|
5
|
+
|
|
6
|
+
base: parse -> [0:acl] -> [1:nat] -> route -> rewrite
|
|
7
|
+
|
|
8
|
+
i.e. the fixed base (parse … route → rewrite) with the ordered inline NF modules in the
|
|
9
|
+
middle. This module turns that into the ordered functions actually running in the router,
|
|
10
|
+
so the Router Lab can show the *deployed* chain next to the *edited* one. Pure/text-only.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import re
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
|
|
17
|
+
_MOD_RE = re.compile(r"\[(\d+):([^\]]+)\]")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class DeployedModule:
|
|
22
|
+
index: int
|
|
23
|
+
type: str # gRouter module type: acl | nat | counter | block | lua | filter | …
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def parse_chain(text: str) -> list[DeployedModule]:
|
|
27
|
+
"""The ordered inline NF modules currently deployed in the router (from `gpipe list`)."""
|
|
28
|
+
return [DeployedModule(int(i), t.strip()) for i, t in _MOD_RE.findall(text or "")]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def chain_summary(text: str) -> str:
|
|
32
|
+
"""A compact one-line view of the deployed chain, e.g. 'parse → acl → nat → route → rewrite'."""
|
|
33
|
+
mods = parse_chain(text)
|
|
34
|
+
if not mods:
|
|
35
|
+
return "parse → route → rewrite (no service functions deployed)"
|
|
36
|
+
return "parse → " + " → ".join(m.type for m in mods) + " → route → rewrite"
|