From 53bdf0c7a4d5e5da66c03f4989a958c620a28329 Mon Sep 17 00:00:00 2001 From: Thomas Walker Lynch Date: Wed, 14 Oct 2026 04:53:43 +0000 Subject: [PATCH] start of edits for Subu v4 --- .gitignore | 34 +- .../how-to_add-project-dependency.html | 119 +++++++ administrator/document/how-to_release.html | 71 ++-- administrator/tool/RT-Style_header_v5_to_v6 | 306 ++++++++++++++++++ administrator/tool/html_to_v31 | 63 ++++ administrator/tool/new-project | 87 ++++- administrator/tool/skeleton_diff | 216 +++++++++++++ consumer/.gitignore | 5 + .../authored}/.gitkeep | 0 .../administrator}/document/setup.js | 0 .../deprecated/developer}/DNS/.gitignore | 0 .../developer}/DNS/10-block-IPv6.nft | 0 .../developer}/DNS/20-SUBU-ports.nft | 0 .../DNS/Man_In_Grey_input_acceptance.py | 0 .../deprecated/developer}/DNS/README.org | 0 .../developer}/DNS/doc_howto_install.org | 0 .../DNS/stage-US/10-block-IPv6.nft.py | 0 .../DNS/stage-US/20-SUBU-ports-US.nft.py | 0 .../DNS/stage-US/30-unbound-US.conf.py | 0 .../DNS/stage-US/50-wg-policy-US.service.py | 0 .../DNS/stage-US/51-wg-policy-US.sh.py | 0 .../developer}/DNS/stage_0/30-dnsredir.nft | 0 .../developer}/DNS/stage_0/unbound-US.conf | 0 .../developer}/DNS/stage_0/unbound-x6.conf | 0 .../developer}/DNS/stage_0/unbound@.service | 0 .../deprecated/developer}/DNS/unbound_US.py | 0 .../developer}/DNS/unbound_at_template.py | 0 .../deprecated/developer}/DNS/unbound_x6.py | 0 .../deprecated/developer}/cc/Db.lib.c | 0 .../deprecated/developer}/cc/DbSubu.lib.c | 0 .../deprecated/developer}/cc/Db_close.cli.c | 0 .../deprecated/developer}/cc/Hello.cli.c | 0 .../deprecated/developer}/cc/Hello.lib.c | 0 .../deprecated/developer}/cc/README.org | 0 .../deprecated/developer}/cc/Server.cli.c | 0 .../deprecated/developer}/cc/Server.lib.c | 0 .../developer}/cc/db_add_user.cli.c | 0 .../developer}/cc/db_delete_user.cli.c | 0 .../developer}/cc/db_log_event.cli.c | 0 .../deprecated/developer}/cc/db_open.cli.c | 0 .../developer}/cc/db_validate_schema.cli.c | 0 .../developer}/cc/scratchpad/.gitignore | 0 .../developer}/cc/sqlite/schema.sql | 0 .../developer}/deprecated/.gitignore | 0 .../device_management/bestow_audio.sh | 0 .../developer}/login/logout_subu.sh | 0 .../deprecated/developer}/login/subu_login | 0 .../deprecated/developer}/manager/CLI/CLI.py | 0 .../developer}/manager/CLI/dispatch.py | 0 .../developer}/manager/CLI/domain/device.py | 0 .../developer}/manager/CLI/domain/exec.py | 0 .../developer}/manager/CLI/domain/network.py | 0 .../developer}/manager/CLI/domain/options.py | 0 .../developer}/manager/CLI/domain/subu.py | 0 .../developer}/manager/CLI/domain/wg.py | 0 .../deprecated/developer}/manager/CLI/env.py | 0 .../manager/CLI/infrastructure/bpf.py | 0 .../CLI/infrastructure/bpf_force_egress.c | 0 .../manager/CLI/infrastructure/bpf_worker.py | 0 .../manager/CLI/infrastructure/db.py | 0 .../manager/CLI/infrastructure/device.py | 0 .../CLI/infrastructure/options_store.py | 0 .../manager/CLI/infrastructure/schema.sql | 0 .../manager/CLI/infrastructure/unix.py | 0 .../deprecated/developer}/manager/CLI/text.py | 0 .../deprecated/developer}/manager/install | 0 .../manager/systemd/boot_attach.service | 0 .../manager/systemd/subu_resume.service | 0 .../manager/usr_local_bin/boot_attach | 0 .../deprecated/developer}/mount/README.org | 0 .../mount/device_mapname__open_mount.sh | 0 .../developer}/mount/device_umount.sh | 0 .../developer}/mount/disable_linger.sh | 0 .../developer}/mount/logout_subu.sh | 0 .../mount/mapname__mounted_masu_list.sh | 0 .../developer}/mount/mapname_masu__to_home.sh | 0 .../developer}/mount/masu__map_own_all.sh | 0 .../mount/masu__subu_data_dir_list.sh | 0 .../developer}/mount/masu__subu_dir_list.sh | 0 .../developer}/mount/masu_subu__map_own.sh | 0 .../mount/masu_subu__map_own_orig.sh | 0 .../developer}/mount/masu_subu__uid.sh | 0 .../developer}/mount/mount_pt_list.sh | 0 .../deprecated/developer}/tool/clean | 0 .../deprecated/developer}/tool/env | 0 .../deprecated/developer}/tool/make | 0 .../deprecated/developer}/tool/makefile | 0 .../deprecated/developer}/tool/mockup_update | 0 .../deprecated/developer}/tool/release | 0 .../deprecated/developer}/tool/rm_pycache | 0 .../developer}/tunnel-client/.gitignore | 0 .../developer}/tunnel-client/db/.gitignore | 0 .../tunnel-client/db_bind_user_to_iface.py | 0 .../developer}/tunnel-client/db_checks.py | 0 .../tunnel-client/db_init_StanleyPark.py | 0 .../developer}/tunnel-client/db_init_iface.py | 0 .../tunnel-client/db_init_iface_US.py | 0 .../tunnel-client/db_init_iface_x6.py | 0 .../db_init_ip_iface_addr_assign.py | 0 .../db_init_ip_table_registration.py | 0 .../tunnel-client/db_init_route_defaults.py | 0 .../tunnel-client/db_init_server_US.py | 0 .../tunnel-client/db_init_server_incommon.py | 0 .../tunnel-client/db_init_server_x6.py | 0 .../developer}/tunnel-client/db_schema.sql | 0 .../tunnel-client/db_schema_load.sh | 0 .../developer}/tunnel-client/db_wipe.py | 0 .../tunnel-client/deploy_StanleyPark.py | 0 .../tunnel-client/deprecated/.gitignore | 0 .../tunnel-client/doc_IP_terminaology.org | 0 .../tunnel-client/doc_StanleyPark.org | 0 .../developer}/tunnel-client/doc_config.org | 0 .../developer}/tunnel-client/doc_keys.org | 0 .../tunnel-client/doc_stage_progs.org | 0 .../developer}/tunnel-client/iface_down.py | 0 .../developer}/tunnel-client/iface_status.py | 0 .../developer}/tunnel-client/iface_up.sh | 0 .../developer}/tunnel-client/incommon.py | 0 .../developer}/tunnel-client/inspect.sh | 0 .../developer}/tunnel-client/inspect_1.py | 0 .../inspect_client_public_key.py | 0 .../tunnel-client/install_staged_tree.py | 0 .../developer}/tunnel-client/key/.gitignore | 0 .../tunnel-client/key_client_generate.py | 0 .../tunnel-client/key_server_set.py | 0 .../developer}/tunnel-client/ls_iface.py | 0 .../developer}/tunnel-client/ls_key.py | 0 .../developer}/tunnel-client/ls_server.py | 0 .../tunnel-client/ls_server_setting.py | 0 .../developer}/tunnel-client/ls_servers.sh | 0 .../developer}/tunnel-client/ls_user.py | 0 .../tunnel-client/manual_reference.org | 0 .../developer}/tunnel-client/manual_user.org | 0 .../tunnel-client/scratchpad/.gitignore | 0 .../developer}/tunnel-client/stage/.gitignore | 0 .../tunnel-client/stage_IP_apply_script.py | 0 .../tunnel-client/stage_StanleyPark.py | 0 .../developer}/tunnel-client/stage_client.py | 0 .../developer}/tunnel-client/stage_wg_conf.py | 0 .../developer}/tunnel-client/stage_wipe.py | 0 .../developer}/tunnel-client/start_iface.py | 0 .../tunnel-client/stop_clean_iface.py | 0 .../developer}/tunnel-client/todo.org | 0 .../tunnel-client/wg_keys_incommon.py | 0 .../tunnel-server/set_client_key.sh | 0 .../developer}/tunnel-server/setup.sh | 0 .../deprecated/release}/manager/CLI.py | 0 .../deprecated/release}/manager/dispatch.py | 0 .../release}/manager/domain/exec.py | 0 .../release}/manager/domain/network.py | 0 .../release}/manager/domain/options.py | 0 .../release}/manager/domain/subu.py | 0 .../deprecated/release}/manager/domain/wg.py | 0 .../deprecated/release}/manager/env.py | 0 .../release}/manager/infrastructure/bpf.py | 0 .../manager/infrastructure/bpf_force_egress.c | 0 .../manager/infrastructure/bpf_worker.py | 0 .../release}/manager/infrastructure/db.py | 0 .../manager/infrastructure/options_store.py | 0 .../manager/infrastructure/schema.sql | 0 .../release}/manager/infrastructure/unix.py | 0 .../deprecated/release}/manager/text.py | 0 .../release}/manager/uncatelogued/parser.py | 0 .../shared}/dictionary_style-directory.js | 0 .../shared}/document/install_Python.org | 0 .../shared}/document/install_generic.org | 0 .../shared}/document/installation_Python.org | 0 .../shared}/document/installation_generic.org | 0 .../shared}/document/isntall_RT_share.org | 0 .../deprecated/shared}/document/packages.org | 0 .../deprecated/shared}/document/setup.js | 0 .../shared/linked-project/.gitkeep} | 0 .../shared}/linked-project/RT-project-share | 0 .../shared}/linked-project/RT-style-JS_public | 0 .../deprecated/shared}/tool/RT-formatter | 0 .../deprecated/shared}/tool/RT-formatter.el | 0 .../deprecated/shared}/upstream/.gitignore | 0 .../authored/deprecated/tester}/manager/sm | 0 .../deprecated/tester}/manager/test_0.sh | 0 .../tester}/manager/test_0_driver.sh | 0 .../tester}/manager/test_0_expected.sh | 0 .../deprecated/tester}/manager/test_0_out.sh | 0 .../tester}/manager/test_1_driver.sh | 0 .../authored/deprecated/tester}/test.sh | 0 .../authored/deprecated/tester}/test_0.sh | 0 .../deprecated/tester}/test_0_expected.sh | 0 .../deprecated/tester}/tool/developer | 0 .../authored/deprecated/tester}/tool/env | 0 .../authored/deprecated/tester}/tool/publish | 0 .../authored/deprecated/tester}/tool/tester | 0 .../deprecated/tester}/tool/tester_lib.py | 0 developer/deprecated/server.lib.c | 104 ------ .../document/SubU_window_Thomas_developer.png | Bin 0 -> 60772 bytes .../document/naming_file-and-directory.html | 101 ++++++ .../single-file_C-module-and-namespace.html | 184 +++++++++++ .../experiment/.gitkeep | 0 developer/scratchpad/.gitignore | 2 + developer/tool/build | 123 +++++++ developer/tool/build_component/copy | 59 ++++ developer/tool/build_component/make | 15 + developer/tool/build_component/makefile | 55 ++++ developer/tool/build_component/sync_engine.py | 80 +++++ developer/tool/promote | 261 +++++++++++++++ developer/tool/setup | 3 + document/introduction_Harmony.html | 249 ++++++++++++++ ...role-and-workflow_product-development.html | 134 ++++++++ ...role-and-workflow_product-maintenance.html | 111 +++++++ document/writing_tool-script.html | 287 ++++++++++++++++ setup | 114 ++++--- shared/project-dependency/.gitignore | 11 + shared/project-dependency/Python-3.12.3 | 1 + shared/project-dependency/Python.init | 16 + shared/project-dependency/Python.org | 38 +++ shared/project-dependency/project | 1 + .../.githolder => shared/tool/PATH.sh | 0 shared/tool/env_to_emacs | 30 ++ shared/tool/setup | 87 ++--- shared/tool/version | 7 +- tester/{document/.githolder => .gitkeep} | 0 .../{shell/.githolder => authored/.gitkeep} | 0 tester/tool/setup | 3 + tool/.githolder | 0 222 files changed, 2704 insertions(+), 273 deletions(-) create mode 100644 administrator/document/how-to_add-project-dependency.html create mode 100755 administrator/tool/RT-Style_header_v5_to_v6 create mode 100755 administrator/tool/html_to_v31 create mode 100755 administrator/tool/skeleton_diff create mode 100644 consumer/.gitignore rename {shared/linked-project => developer/authored}/.gitkeep (100%) rename {administrator => developer/authored/deprecated/administrator}/document/setup.js (100%) rename developer/{ => authored/deprecated/developer}/DNS/.gitignore (100%) rename developer/{ => authored/deprecated/developer}/DNS/10-block-IPv6.nft (100%) rename developer/{ => authored/deprecated/developer}/DNS/20-SUBU-ports.nft (100%) rename developer/{ => authored/deprecated/developer}/DNS/Man_In_Grey_input_acceptance.py (100%) rename developer/{ => authored/deprecated/developer}/DNS/README.org (100%) rename developer/{ => authored/deprecated/developer}/DNS/doc_howto_install.org (100%) rename developer/{ => authored/deprecated/developer}/DNS/stage-US/10-block-IPv6.nft.py (100%) rename developer/{ => authored/deprecated/developer}/DNS/stage-US/20-SUBU-ports-US.nft.py (100%) rename developer/{ => authored/deprecated/developer}/DNS/stage-US/30-unbound-US.conf.py (100%) rename developer/{ => authored/deprecated/developer}/DNS/stage-US/50-wg-policy-US.service.py (100%) rename developer/{ => authored/deprecated/developer}/DNS/stage-US/51-wg-policy-US.sh.py (100%) rename developer/{ => authored/deprecated/developer}/DNS/stage_0/30-dnsredir.nft (100%) rename developer/{ => authored/deprecated/developer}/DNS/stage_0/unbound-US.conf (100%) rename developer/{ => authored/deprecated/developer}/DNS/stage_0/unbound-x6.conf (100%) rename developer/{ => authored/deprecated/developer}/DNS/stage_0/unbound@.service (100%) rename developer/{ => authored/deprecated/developer}/DNS/unbound_US.py (100%) rename developer/{ => authored/deprecated/developer}/DNS/unbound_at_template.py (100%) rename developer/{ => authored/deprecated/developer}/DNS/unbound_x6.py (100%) rename developer/{ => authored/deprecated/developer}/cc/Db.lib.c (100%) rename developer/{ => authored/deprecated/developer}/cc/DbSubu.lib.c (100%) rename developer/{ => authored/deprecated/developer}/cc/Db_close.cli.c (100%) rename developer/{ => authored/deprecated/developer}/cc/Hello.cli.c (100%) rename developer/{ => authored/deprecated/developer}/cc/Hello.lib.c (100%) rename developer/{ => authored/deprecated/developer}/cc/README.org (100%) rename developer/{ => authored/deprecated/developer}/cc/Server.cli.c (100%) rename developer/{ => authored/deprecated/developer}/cc/Server.lib.c (100%) rename developer/{ => authored/deprecated/developer}/cc/db_add_user.cli.c (100%) rename developer/{ => authored/deprecated/developer}/cc/db_delete_user.cli.c (100%) rename developer/{ => authored/deprecated/developer}/cc/db_log_event.cli.c (100%) rename developer/{ => authored/deprecated/developer}/cc/db_open.cli.c (100%) rename developer/{ => authored/deprecated/developer}/cc/db_validate_schema.cli.c (100%) rename developer/{ => authored/deprecated/developer}/cc/scratchpad/.gitignore (100%) rename developer/{ => authored/deprecated/developer}/cc/sqlite/schema.sql (100%) rename developer/{ => authored/deprecated/developer}/deprecated/.gitignore (100%) rename developer/{ => authored/deprecated/developer}/device_management/bestow_audio.sh (100%) rename developer/{ => authored/deprecated/developer}/login/logout_subu.sh (100%) rename developer/{ => authored/deprecated/developer}/login/subu_login (100%) rename developer/{ => authored/deprecated/developer}/manager/CLI/CLI.py (100%) rename developer/{ => authored/deprecated/developer}/manager/CLI/dispatch.py (100%) rename developer/{ => authored/deprecated/developer}/manager/CLI/domain/device.py (100%) rename developer/{ => authored/deprecated/developer}/manager/CLI/domain/exec.py (100%) rename developer/{ => authored/deprecated/developer}/manager/CLI/domain/network.py (100%) rename developer/{ => authored/deprecated/developer}/manager/CLI/domain/options.py (100%) rename developer/{ => authored/deprecated/developer}/manager/CLI/domain/subu.py (100%) rename developer/{ => authored/deprecated/developer}/manager/CLI/domain/wg.py (100%) rename developer/{ => authored/deprecated/developer}/manager/CLI/env.py (100%) rename developer/{ => authored/deprecated/developer}/manager/CLI/infrastructure/bpf.py (100%) rename developer/{ => authored/deprecated/developer}/manager/CLI/infrastructure/bpf_force_egress.c (100%) rename developer/{ => authored/deprecated/developer}/manager/CLI/infrastructure/bpf_worker.py (100%) rename developer/{ => authored/deprecated/developer}/manager/CLI/infrastructure/db.py (100%) rename developer/{ => authored/deprecated/developer}/manager/CLI/infrastructure/device.py (100%) rename developer/{ => authored/deprecated/developer}/manager/CLI/infrastructure/options_store.py (100%) rename developer/{ => authored/deprecated/developer}/manager/CLI/infrastructure/schema.sql (100%) rename developer/{ => authored/deprecated/developer}/manager/CLI/infrastructure/unix.py (100%) rename developer/{ => authored/deprecated/developer}/manager/CLI/text.py (100%) rename developer/{ => authored/deprecated/developer}/manager/install (100%) rename developer/{ => authored/deprecated/developer}/manager/systemd/boot_attach.service (100%) rename developer/{ => authored/deprecated/developer}/manager/systemd/subu_resume.service (100%) rename developer/{ => authored/deprecated/developer}/manager/usr_local_bin/boot_attach (100%) rename developer/{ => authored/deprecated/developer}/mount/README.org (100%) rename developer/{ => authored/deprecated/developer}/mount/device_mapname__open_mount.sh (100%) rename developer/{ => authored/deprecated/developer}/mount/device_umount.sh (100%) rename developer/{ => authored/deprecated/developer}/mount/disable_linger.sh (100%) rename developer/{ => authored/deprecated/developer}/mount/logout_subu.sh (100%) rename developer/{ => authored/deprecated/developer}/mount/mapname__mounted_masu_list.sh (100%) rename developer/{ => authored/deprecated/developer}/mount/mapname_masu__to_home.sh (100%) rename developer/{ => authored/deprecated/developer}/mount/masu__map_own_all.sh (100%) rename developer/{ => authored/deprecated/developer}/mount/masu__subu_data_dir_list.sh (100%) rename developer/{ => authored/deprecated/developer}/mount/masu__subu_dir_list.sh (100%) rename developer/{ => authored/deprecated/developer}/mount/masu_subu__map_own.sh (100%) rename developer/{ => authored/deprecated/developer}/mount/masu_subu__map_own_orig.sh (100%) rename developer/{ => authored/deprecated/developer}/mount/masu_subu__uid.sh (100%) rename developer/{ => authored/deprecated/developer}/mount/mount_pt_list.sh (100%) rename developer/{ => authored/deprecated/developer}/tool/clean (100%) rename developer/{ => authored/deprecated/developer}/tool/env (100%) rename developer/{ => authored/deprecated/developer}/tool/make (100%) rename developer/{ => authored/deprecated/developer}/tool/makefile (100%) rename developer/{ => authored/deprecated/developer}/tool/mockup_update (100%) rename developer/{ => authored/deprecated/developer}/tool/release (100%) rename developer/{ => authored/deprecated/developer}/tool/rm_pycache (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/.gitignore (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/db/.gitignore (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/db_bind_user_to_iface.py (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/db_checks.py (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/db_init_StanleyPark.py (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/db_init_iface.py (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/db_init_iface_US.py (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/db_init_iface_x6.py (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/db_init_ip_iface_addr_assign.py (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/db_init_ip_table_registration.py (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/db_init_route_defaults.py (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/db_init_server_US.py (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/db_init_server_incommon.py (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/db_init_server_x6.py (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/db_schema.sql (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/db_schema_load.sh (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/db_wipe.py (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/deploy_StanleyPark.py (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/deprecated/.gitignore (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/doc_IP_terminaology.org (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/doc_StanleyPark.org (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/doc_config.org (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/doc_keys.org (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/doc_stage_progs.org (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/iface_down.py (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/iface_status.py (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/iface_up.sh (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/incommon.py (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/inspect.sh (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/inspect_1.py (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/inspect_client_public_key.py (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/install_staged_tree.py (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/key/.gitignore (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/key_client_generate.py (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/key_server_set.py (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/ls_iface.py (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/ls_key.py (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/ls_server.py (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/ls_server_setting.py (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/ls_servers.sh (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/ls_user.py (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/manual_reference.org (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/manual_user.org (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/scratchpad/.gitignore (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/stage/.gitignore (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/stage_IP_apply_script.py (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/stage_StanleyPark.py (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/stage_client.py (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/stage_wg_conf.py (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/stage_wipe.py (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/start_iface.py (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/stop_clean_iface.py (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/todo.org (100%) rename developer/{ => authored/deprecated/developer}/tunnel-client/wg_keys_incommon.py (100%) rename developer/{ => authored/deprecated/developer}/tunnel-server/set_client_key.sh (100%) rename developer/{ => authored/deprecated/developer}/tunnel-server/setup.sh (100%) rename {release => developer/authored/deprecated/release}/manager/CLI.py (100%) rename {release => developer/authored/deprecated/release}/manager/dispatch.py (100%) rename {release => developer/authored/deprecated/release}/manager/domain/exec.py (100%) rename {release => developer/authored/deprecated/release}/manager/domain/network.py (100%) rename {release => developer/authored/deprecated/release}/manager/domain/options.py (100%) rename {release => developer/authored/deprecated/release}/manager/domain/subu.py (100%) rename {release => developer/authored/deprecated/release}/manager/domain/wg.py (100%) rename {release => developer/authored/deprecated/release}/manager/env.py (100%) rename {release => developer/authored/deprecated/release}/manager/infrastructure/bpf.py (100%) rename {release => developer/authored/deprecated/release}/manager/infrastructure/bpf_force_egress.c (100%) rename {release => developer/authored/deprecated/release}/manager/infrastructure/bpf_worker.py (100%) rename {release => developer/authored/deprecated/release}/manager/infrastructure/db.py (100%) rename {release => developer/authored/deprecated/release}/manager/infrastructure/options_store.py (100%) rename {release => developer/authored/deprecated/release}/manager/infrastructure/schema.sql (100%) rename {release => developer/authored/deprecated/release}/manager/infrastructure/unix.py (100%) rename {release => developer/authored/deprecated/release}/manager/text.py (100%) rename {release => developer/authored/deprecated/release}/manager/uncatelogued/parser.py (100%) rename {shared => developer/authored/deprecated/shared}/dictionary_style-directory.js (100%) rename {shared => developer/authored/deprecated/shared}/document/install_Python.org (100%) rename {shared => developer/authored/deprecated/shared}/document/install_generic.org (100%) rename {shared => developer/authored/deprecated/shared}/document/installation_Python.org (100%) rename {shared => developer/authored/deprecated/shared}/document/installation_generic.org (100%) rename {shared => developer/authored/deprecated/shared}/document/isntall_RT_share.org (100%) rename {shared => developer/authored/deprecated/shared}/document/packages.org (100%) rename {shared => developer/authored/deprecated/shared}/document/setup.js (100%) rename developer/{document/.githolder => authored/deprecated/shared/linked-project/.gitkeep} (100%) rename {shared => developer/authored/deprecated/shared}/linked-project/RT-project-share (100%) rename {shared => developer/authored/deprecated/shared}/linked-project/RT-style-JS_public (100%) rename {shared => developer/authored/deprecated/shared}/tool/RT-formatter (100%) rename {shared => developer/authored/deprecated/shared}/tool/RT-formatter.el (100%) rename {shared => developer/authored/deprecated/shared}/upstream/.gitignore (100%) rename {tester => developer/authored/deprecated/tester}/manager/sm (100%) rename {tester => developer/authored/deprecated/tester}/manager/test_0.sh (100%) rename {tester => developer/authored/deprecated/tester}/manager/test_0_driver.sh (100%) rename {tester => developer/authored/deprecated/tester}/manager/test_0_expected.sh (100%) rename {tester => developer/authored/deprecated/tester}/manager/test_0_out.sh (100%) rename {tester => developer/authored/deprecated/tester}/manager/test_1_driver.sh (100%) rename {tester => developer/authored/deprecated/tester}/test.sh (100%) rename {tester => developer/authored/deprecated/tester}/test_0.sh (100%) rename {tester => developer/authored/deprecated/tester}/test_0_expected.sh (100%) rename {tester => developer/authored/deprecated/tester}/tool/developer (100%) rename {tester => developer/authored/deprecated/tester}/tool/env (100%) rename {tester => developer/authored/deprecated/tester}/tool/publish (100%) rename {tester => developer/authored/deprecated/tester}/tool/tester (100%) rename {tester => developer/authored/deprecated/tester}/tool/tester_lib.py (100%) delete mode 100644 developer/deprecated/server.lib.c create mode 100644 developer/document/SubU_window_Thomas_developer.png create mode 100644 developer/document/naming_file-and-directory.html create mode 100644 developer/document/single-file_C-module-and-namespace.html rename document/.githolder => developer/experiment/.gitkeep (100%) create mode 100644 developer/scratchpad/.gitignore create mode 100755 developer/tool/build create mode 100755 developer/tool/build_component/copy create mode 100755 developer/tool/build_component/make create mode 100644 developer/tool/build_component/makefile create mode 100644 developer/tool/build_component/sync_engine.py create mode 100755 developer/tool/promote create mode 100644 developer/tool/setup create mode 100644 document/introduction_Harmony.html create mode 100644 document/role-and-workflow_product-development.html create mode 100644 document/role-and-workflow_product-maintenance.html create mode 100644 document/writing_tool-script.html mode change 100644 => 100755 setup create mode 100644 shared/project-dependency/.gitignore create mode 120000 shared/project-dependency/Python-3.12.3 create mode 100644 shared/project-dependency/Python.init create mode 100644 shared/project-dependency/Python.org create mode 120000 shared/project-dependency/project rename tester/deprecated/.githolder => shared/tool/PATH.sh (100%) create mode 100755 shared/tool/env_to_emacs rename tester/{document/.githolder => .gitkeep} (100%) rename tester/{shell/.githolder => authored/.gitkeep} (100%) create mode 100644 tester/tool/setup delete mode 100644 tool/.githolder diff --git a/.gitignore b/.gitignore index 801694e..9538920 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,34 @@ -__pycache__ +# no archive or temporary files +*.tar +*.tgz +*.zip +*. +temp.* +**/scratchpad/* +!**/scratchpad/.gitignore + + +# Python +__pycache__/ +*.py[cod] +*.pyo +*.pyd +.ipynb_checkpoints/ +.pytest_cache/ + +# C / compiled artifacts +*.o +*.a +*.out +*.so + +# OS and editor backup files *~ -*.sqlite3 \ No newline at end of file +*.bak +*.swp +.DS_Store + +# SubU +__pycache__ +*.sqlite3 diff --git a/administrator/document/how-to_add-project-dependency.html b/administrator/document/how-to_add-project-dependency.html new file mode 100644 index 0000000..327a28c --- /dev/null +++ b/administrator/document/how-to_add-project-dependency.html @@ -0,0 +1,119 @@ + + + + + How to add a project dependency + + + + + + + + + + + +

Project dependencies

+ +

+ A project dependency is software the project uses but did not author: a language toolchain, a library, a tool, or another project. A dependency is installed outside the project, as a sibling directory of it, and is linked into the project under shared/project-dependency. +

+ +

+ Getting an arbitrary piece of software working is different every time, so the administrator is free to do whatever a dependency needs inside shared/project-dependency. The skeleton relies on only the few conventions given in the next section. +

+ +

What the skeleton relies on

+ + + +

+ Files for one dependency share a base name, so that Python.init, Python.org, and the link they describe sit together in a listing. +

+ +

Steps

+ +

Install the dependency beside the project

+

+ Download, clone, or extract the dependency into the directory that holds the project, so that it sits as a sibling of the project directory. Build it there if it needs building. +

+ +

Link it

+

+ Make the link through the project link. The word project in the link target is literal. +

+ + > cd $REPO_HOME/shared/project-dependency + > ln -s project/<dependency directory> <name> + + +

Write the init file

+

+ Write <name>.init in the same directory. Because it runs on every setup for every role, keep it to setting the environment: +

+ +

+ Python.init, which comes with the skeleton, is an example: +

+ + if [[ -d "$REPO_HOME/shared/project-dependency/Python-3.12.3/bin" ]]; then + PATH="$REPO_HOME/shared/project-dependency/Python-3.12.3/bin:$PATH" + else + if [[ "$ROLE" == "administrator" ]]; then + echo "Notice: Local Python-3.12.3 bin not found. Using system Python." + fi + fi + + +

Write the installation notes

+

+ Record what was installed, where it came from, and the commands used, in <name>.org, <name>.html, or comments in the init file. A team member who has cloned the project follows these notes to install the dependency, and then recreates the link if it is not tracked. +

+ +

Decide what is tracked

+

+ The skeleton's .gitignore tracks itself, the project link, and files ending in .init, .install, .org, and .html. Everything else in the directory is ignored. To track a dependency's link, so that it arrives with every clone, add a line naming it: +

+ + !<name> + +

+ A tracked link points at nothing until the dependency is installed beside the project, which is why init files test for the dependency before using it. +

+ +

Commit

+

+ Add the init file, the notes, the .gitignore change, and any tracked link, then commit. +

+ +
+ + diff --git a/administrator/document/how-to_release.html b/administrator/document/how-to_release.html index 888d414..0f33c4a 100644 --- a/administrator/document/how-to_release.html +++ b/administrator/document/how-to_release.html @@ -1,85 +1,98 @@ + + Release howto - + - - + + - + - +

Prerequisites

- A release requires a tested promotion candidate. The developer compiles the code and promotes it to the consumer/made directory. The tester validates the candidate. The product manager gives the order to release. + A developer works in the developer branch of the project, and makes use of the build and promote tools to stage release candidates into the $REPO_HOME/consumer directory. The tester then validates the release candidate. This constitutes a cycle of work, developing, promoting candidates, testing, and then further developing. Once the project manager agrees this cycle is complete, the release flow moves to the next step.

-

Create the release branch

+

Make the release branch

- The administrator executes the release. Open a terminal. Enter the project workspace. + The administrator executes the release. Open a terminal. Enter the project workspace.

- + > . setup administrator - +

Switch to the core development branch. Ensure the local repository is current.

- - > git checkout core_developer_branch + + > git checkout core-developer_branch > git pull - +

- Create a new branch for the release. Use the major version number. + Make a new branch for the release. Use the major version number.

- + > git checkout -b release_v<major> - +

Issue management and patching

- Only critical issues receive patches on a release branch. The product manager defines what is critical. If approved, the release team applies the fix directly to the existing release_v<major> branch. + Only critical issues receive patches on a release branch. The product manager defines what is critical. If approved, the release team applies the fix directly to the existing release_v<major> branch.

- The branch name release_v<major> remains static. The code on the branch is updated. The administrator advances the minor release number in the shared/tool/version file. + The branch name release_v<major> remains static. The code on the branch is updated. The administrator advances the minor release number in the shared/tool/version file.

- The administrator tags the new commit with the full version number, such as v<major>.<minor>. Minor versions are edits on the major release branch. They are visible to a person by running the version command. + The administrator tags the new commit with the full version number, such as v<major>.<minor>. Minor versions are edits on the major release branch. They are visible to a person by running the version command.

- The developer must ensure the fix is also ported to the core_developer_branch. This prevents the defect from reappearing in future major releases. + The developer must ensure the fix is also ported to the core-developer_branch. This prevents the defect from reappearing in future major releases.

Tag and push

Tag the release with the exact version number.

- + > git tag -a v<major>.<minor> -m "Release v<major>.<minor>" - +

Push the branch to the remote repository. Push the tags.

- + > git push origin release_v<major> > git push origin --tags - +

Repository default branch

- Set the repository default branch to the new release_v<major> branch on the hosting platform. This ensures a person cloning or pulling the repository receives the most recent major release code by default. + Set the repository default branch to the new release_v<major> branch on the hosting platform. This ensures a person cloning or pulling the repository receives the most recent major release code by default.

Verification

@@ -96,6 +109,6 @@
  • Project Manager: Participates in release issue triage. Assesses impact to the overall project schedule.
  • -
    + diff --git a/administrator/tool/RT-Style_header_v5_to_v6 b/administrator/tool/RT-Style_header_v5_to_v6 new file mode 100755 index 0000000..3ca4169 --- /dev/null +++ b/administrator/tool/RT-Style_header_v5_to_v6 @@ -0,0 +1,306 @@ +#!/usr/bin/env python3 +# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*- + +""" +RT-Style_header_v5_to_v6 + +Converts RT-Style version 5 document headers to version 6. + +A version 5 document loads the engine through a locator file beside it: + + + +A version 6 document carries an inline header block instead. The block takes +the library from the record made by opening a Manuscript directory's +setup.html, and falls back to the Reasoning Technology server. + +For each version 5 .html file found, the tool replaces the locator line in + with the version 6 block, keeping its indentation. It then deletes +RT-Manuscript_locator.js from that file's directory, unless another version 5 +document there, not converted in this run, still uses it. Any other mention of +RT-Manuscript_locator in the file (an escaped example, prose) is logged for a +person to edit. + +Usage: RT-Style_header_v5_to_v6 [file ...] + RT-Style_header_v5_to_v6 preview [file ...] + + help | -h | --help print this message (also printed when given no arguments) + project walk the directory tree rooted at $REPO_HOME + tree walk the directory tree rooted at the current directory + local only the current directory, no subdirectories + file ... only the named .html files + preview print the log of what would do, change nothing + +Every directory is walked, whatever its name. Symbolic links to directories +are not followed, so a link back up the tree cannot cause a loop or reach +outside it. +""" + +import sys ,os ,re +from pathlib import Path + +LOCATOR_FILE = "RT-Manuscript_locator.js" +V6_MARK = 'localStorage.getItem("RT.dirpr_library")' + +V6_BLOCK = [ + '' +] + +LOCATOR_LINE = re.compile( + r'^(?P[ \t]*)\s*\s*$' + ,re.IGNORECASE +) +HEAD_END = re.compile(r'' ,re.IGNORECASE) + + +# ---------------------------------------------------------------- log + +class Log: + def __init__(self ,preview: bool ,base: Path): + self.preview = preview + self.base = base + self.count = {"converted": 0 ,"deleted": 0 ,"already": 0 ,"hand": 0 + ,"untouched": 0 ,"stray": 0 ,"error": 0} + + def rel(self ,p: Path) -> str: + try: return str(p.resolve().relative_to(self.base)) + except ValueError: return str(p) + + def line(self ,tag: str ,text: str): + print(f" {tag:<15} {text}") + + def act(self ,done: str ,would: str ,p: Path ,key: str): + self.line(would if self.preview else done ,self.rel(p)) + self.count[key] += 1 + + def summary(self): + c = self.count + v = "would be " if self.preview else "" + print() + print("summary:") + print(f" {c['converted']:>4} .html files {v}converted to version 6") + print(f" {c['deleted']:>4} {LOCATOR_FILE} files {v}deleted") + print(f" {c['already']:>4} .html files already version 6") + print(f" {c['hand']:>4} .html files with mentions to edit by hand") + print(f" {c['untouched']:>4} other .html files left alone") + if c['stray']: + print(f" {c['stray']:>4} {LOCATOR_FILE} files kept (see 'kept' lines)") + if c['error']: + print(f" {c['error']:>4} errors") + if self.preview: + print("preview only: nothing was changed") + + +# ---------------------------------------------------------------- finding files + +def walk(root: Path ,recursive: bool): + """Yields (directory ,[.html files] ,has_locator) for each directory.""" + if not recursive: + entries = sorted(root.iterdir()) + yield root ,[e for e in entries if e.is_file() and e.suffix == ".html"] ,(root / LOCATOR_FILE).is_file() + return + for d ,dirs ,files in os.walk(root): + dirs.sort() + files = sorted(files) + yield Path(d) ,[Path(d) / f for f in files if f.endswith(".html")] ,LOCATOR_FILE in files + + +# ---------------------------------------------------------------- conversion + +def convert(text: str): + """Returns (new_text ,replaced_count ,leftover). leftover lists + (line_number ,line) for locator mentions that were not replaced.""" + lines = text.split("\n") + out = [] + replaced = 0 + leftover = [] + in_head = True + for n ,line in enumerate(lines ,1): + m = LOCATOR_LINE.match(line) if in_head else None + if m: + eol = "\r" if line.endswith("\r") else "" + out.extend(m.group("indent") + b + eol for b in V6_BLOCK) + replaced += 1 + else: + out.append(line) + if LOCATOR_FILE.removesuffix(".js") in line: + leftover.append((n ,line.strip())) + if in_head and HEAD_END.search(line): + in_head = False + return "\n".join(out) ,replaced ,leftover + +def do_file(f: Path ,log: Log) -> bool: + """Converts one .html file. Returns True when it was version 5.""" + try: + text = f.read_text(encoding="utf-8") + except Exception as e: + log.line("error" ,f"{log.rel(f)}: cannot read: {e}") + log.count["error"] += 1 + return False + + if V6_MARK in text and LOCATOR_FILE.removesuffix(".js") not in text: + log.line("already v6" ,log.rel(f)) + log.count["already"] += 1 + return False + + new_text ,replaced ,leftover = convert(text) + + if replaced: + if not log.preview: + try: + mode = f.stat().st_mode + f.write_text(new_text ,encoding="utf-8") + os.chmod(f ,mode) + except Exception as e: + log.line("error" ,f"{log.rel(f)}: cannot write: {e}") + log.count["error"] += 1 + return False + log.act("converted" ,"would convert" ,f ,"converted") + if replaced > 1: + log.line("note" ,f"{log.rel(f)}: {replaced} locator lines in , all replaced") + elif not leftover: + log.count["untouched"] += 1 + + if leftover: + log.count["hand"] += 1 + for n ,line in leftover: + log.line("edit by hand" ,f"{log.rel(f)}:{n}: {line[:100]}") + + return replaced > 0 + +def uses_locator(f: Path) -> bool: + """True when f still loads the engine through a locator line in .""" + try: + text = f.read_text(encoding="utf-8") + except Exception: + return False + for line in text.split("\n"): + if LOCATOR_LINE.match(line): return True + if HEAD_END.search(line): return False + return False + +def do_locator(d: Path ,converted: set ,log: Log): + """Deletes d's locator unless a version 5 document not converted in this + run still uses it.""" + loc = d / LOCATOR_FILE + users = sorted(f.name for f in d.iterdir() + if f.is_file() and f.suffix == ".html" + and f.resolve() not in converted and uses_locator(f)) + if users: + shown = ", ".join(users[:3]) + (" ..." if len(users) > 3 else "") + log.line("kept" ,f"{log.rel(loc)} (still used by {shown})") + log.count["stray"] += 1 + return + if not log.preview: + try: + loc.unlink() + except Exception as e: + log.line("error" ,f"{log.rel(loc)}: cannot delete: {e}") + log.count["error"] += 1 + return + log.act("deleted" ,"would delete" ,loc ,"deleted") + +def run_dirs(root: Path ,recursive: bool ,log: Log): + for d ,html ,has_locator in walk(root ,recursive): + converted = set() + for f in html: + if do_file(f ,log): converted.add(f.resolve()) + if has_locator: + if converted: + do_locator(d ,converted ,log) + else: + log.line("kept" ,f"{log.rel(d / LOCATOR_FILE)} (no version 5 document beside it)") + log.count["stray"] += 1 + +def run_files(files: list[Path] ,log: Log): + converted = set() + dirs = [] + for f in files: + if do_file(f ,log): + converted.add(f.resolve()) + d = f.parent.resolve() + if d not in dirs: dirs.append(d) + for d in dirs: + if (d / LOCATOR_FILE).is_file(): + do_locator(d ,converted ,log) + + +# ---------------------------------------------------------------- command line + +def print_help() -> int: + print(__doc__.strip()) + return 0 + +def CLI(argv=None) -> int: + if argv is None: + argv = sys.argv[1:] + if not argv or argv[0] in ("help" ,"-h" ,"--help"): + return print_help() + + preview = False + if argv[0] == "preview": + preview = True + argv = argv[1:] + if not argv: + print("Error: 'preview' needs a command after it.\n" ,file=sys.stderr) + print_help() + return 1 + + command ,rest = argv[0] ,argv[1:] + + if command == "file": + if not rest: + print("Error: 'file' needs at least one file name." ,file=sys.stderr) + return 1 + files = [Path(a) for a in rest] + for f in files: + if not f.is_file(): + print(f"Error: {f} is not a file." ,file=sys.stderr) + return 1 + base = Path.cwd().resolve() + log = Log(preview ,base) + print(f"RT-Style_header_v5_to_v6 {'preview ' if preview else ''}file in {base}") + run_files(files ,log) + log.summary() + return 1 if log.count["error"] else 0 + + if rest: + print(f"Error: '{command}' takes no arguments." ,file=sys.stderr) + return 1 + + if command == "project": + repo_home = os.environ.get("REPO_HOME") + if not repo_home: + print("Error: 'project' needs REPO_HOME to be set." ,file=sys.stderr) + return 1 + root ,recursive = Path(repo_home) ,True + elif command == "tree": + root ,recursive = Path.cwd() ,True + elif command == "local": + root ,recursive = Path.cwd() ,False + else: + print(f"Error: unknown command '{command}'.\n" ,file=sys.stderr) + print_help() + return 1 + + if not root.is_dir(): + print(f"Error: {root} is not a directory." ,file=sys.stderr) + return 1 + + base = root.resolve() + log = Log(preview ,base) + print(f"RT-Style_header_v5_to_v6 {'preview ' if preview else ''}{command} in {base}") + run_dirs(base ,recursive ,log) + log.summary() + return 1 if log.count["error"] else 0 + +if __name__ == "__main__": + sys.exit(CLI()) diff --git a/administrator/tool/html_to_v31 b/administrator/tool/html_to_v31 new file mode 100755 index 0000000..b1aba38 --- /dev/null +++ b/administrator/tool/html_to_v31 @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*- +""" +html_update_to_3-1 + +Replaces specific StyleRT script blocks with RT 3.1 loader blocks using +literal string replacement. +""" + +import sys ,os + +OLD_BLOCK = \ +""" + """ + +NEW_BLOCK = \ +""" + """ + +def process_file(file_path: str) -> None: + try: + with open(file_path ,"r" ,encoding="utf-8") as f: + content = f.read() + except Exception as e: + print(f"Error reading {file_path}: {e}" ,file=sys.stderr) + return + + # Normalize line endings to ensure a match across environments + normalized_content = content.replace("\r\n" ,"\n") + normalized_old = OLD_BLOCK.replace("\r\n" ,"\n") + + if( normalized_old in normalized_content ): + new_content = normalized_content.replace(normalized_old ,NEW_BLOCK) + with open(file_path ,"w" ,encoding="utf-8") as f: + f.write(new_content) + print(f"Updated: {file_path}") + else: + print(f"Target lines not found in: {file_path}") + +def CLI() -> None: + args = sys.argv[1:] + + if( not args or "-h" in args or "--help" in args ): + print("Usage: html_update_to_3-1 ") + sys.exit(0) + + for TM_f in args: + process_file(TM_f) + +if __name__ == "__main__": + CLI() diff --git a/administrator/tool/new-project b/administrator/tool/new-project index 07582e2..c949ddb 100755 --- a/administrator/tool/new-project +++ b/administrator/tool/new-project @@ -1,24 +1,63 @@ #!/usr/bin/env python3 # -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*- +""" +new-project + +This administrative tool instantiates a fresh project workspace modeled after +the Harmony skeleton architecture. + +The script executes the following sequential lifecycle: + 1. Validates the execution environment (requires an active role session). + 2. Checks the git status of the skeleton. The new project is made from the + last commit, so uncommitted changes and untracked files would be left + out. When there are any, lists them and asks whether to continue. + 3. Safely extracts a clean copy of all Git-tracked files using an archive stream. + 4. Renames the boilerplate 0pus_Harmony anchor file to reflect the new project. + 5. Injects an initial version tracking entry into the shared configuration space. + 6. Initializes a fresh Git repository bound to the core-developer_branch. + 7. Stages and commits the pristine skeleton infrastructure as a baseline. + 8. Maps any upstream remote network targets passed via CLI parameters. +""" + import sys ,os ,subprocess from pathlib import Path def get_Z_date() -> str: + """Retrieves the current date utilizing the system Z timestamp tool.""" try: res = subprocess.run(["Z" ,"format-%year-%month-%day"] ,capture_output=True ,text=True ,check=True) return res.stdout.strip() except Exception: return "1970-01-01" -def work(project_name: str ,remotes: list[str]) -> int: - repo_home = os.environ.get("REPO_HOME") - if not repo_home: - print("Error: REPO_HOME is not set. A person must run this from within a sourced role environment." ,file=sys.stderr) - return 1 +def uncommitted(repo_path: Path) -> list[str] | None: + """Returns the lines of 'git status --porcelain' for the skeleton: one per + file with uncommitted changes or untracked. None when git cannot report.""" + try: + res = subprocess.run( + ["git" ,"status" ,"--porcelain" ,"--untracked-files=normal"] + ,cwd=str(repo_path) ,capture_output=True ,text=True ,check=True + ) + except Exception: + return None + return [line for line in res.stdout.splitlines() if line.strip()] + +def confirm(question: str) -> bool: + """Asks a yes or no question at the terminal. Without a terminal to ask + at, the answer is no.""" + if not sys.stdin.isatty(): + return False + try: + answer = input(question + " [y/N] ") + except EOFError: + return False + return answer.strip().lower() in ("y" ,"yes") - repo_path = Path(repo_home).resolve() +def work(repo_path: Path ,project_name: str ,remotes: list[str]) -> int: + """Orchestrates the assembly of the new skeleton instance.""" + # Resolve destination relative to the parent space of the current repository dest_path = Path(project_name) if not dest_path.is_absolute(): dest_path = (repo_path.parent / project_name).resolve() @@ -27,20 +66,23 @@ def work(project_name: str ,remotes: list[str]) -> int: print(f"Error: Destination {dest_path} already exists." ,file=sys.stderr) return 1 - print(f"Creating new project at: {dest_path}") + print(f"Making new project at: {dest_path}") dest_path.mkdir(parents=True) + # Export tracked files without duplicating untracked scratchpads or git histories archive_cmd = "git archive HEAD | tar -x -C " + str(dest_path) res = subprocess.run(archive_cmd ,shell=True ,cwd=str(repo_path)) if res.returncode != 0: print("Error extracting archive." ,file=sys.stderr) return 1 + # Personalize the project baseline anchor file opus_src = dest_path / "0pus_Harmony" opus_dst = dest_path / f"0pus_{dest_path.name}" if opus_src.exists(): opus_src.rename(opus_dst) + # Record the lineage in the shared version history version_file = dest_path / "shared" / "tool" / "version" date_str = get_Z_date() version_line = f"echo \"{dest_path.name} v0.1 {date_str}\"\n" @@ -52,11 +94,13 @@ def work(project_name: str ,remotes: list[str]) -> int: with open(version_file ,"w" ,encoding="utf-8") as vf: vf.write(version_line) + # Initialize the pristine Git environment tracking system print("Initializing git repository...") - subprocess.run(["git" ,"init" ,"-b" ,"core_developer_branch"] ,cwd=str(dest_path) ,check=True) + subprocess.run(["git" ,"init" ,"-b" ,"core-developer_branch"] ,cwd=str(dest_path) ,check=True) subprocess.run(["git" ,"add" ,"."] ,cwd=str(dest_path) ,check=True) subprocess.run(["git" ,"commit" ,"-m" ,f"Initial commit of {dest_path.name} from Harmony skeleton"] ,cwd=str(dest_path) ,check=True) + # Configure tracking addresses for upstreams for idx ,remote_url in enumerate(remotes): remote_name = f"remote_{idx}" if "github.com" in remote_url: @@ -69,10 +113,11 @@ def work(project_name: str ,remotes: list[str]) -> int: print(f"Adding remote '{remote_name}': {remote_url}") subprocess.run(["git" ,"remote" ,"add" ,remote_name ,remote_url] ,cwd=str(dest_path) ,check=True) - print(f"Project '{dest_path.name}' successfully created.") + print(f"Project '{dest_path.name}' successfully made.") return 0 def CLI(argv=None) -> int: + """Parses user input tokens from the shell execution frame.""" if argv is None: argv = sys.argv[1:] @@ -104,7 +149,29 @@ def CLI(argv=None) -> int: print("Error: project name is required." ,file=sys.stderr) return 1 - return work(project_name ,remotes) + repo_home = os.environ.get("REPO_HOME") + if not repo_home: + print("Error: REPO_HOME is not set. A person must run this from within a sourced role environment." ,file=sys.stderr) + return 1 + repo_path = Path(repo_home).resolve() + + status = uncommitted(repo_path) + if status is None: + print(f"Error: could not read the git status of {repo_path}." ,file=sys.stderr) + return 1 + if status: + shown = status[:20] + print(f"Warning: {repo_path} has {len(status)} uncommitted or untracked file(s):") + for line in shown: + print(" " + line) + if len(status) > len(shown): + print(f" ... and {len(status) - len(shown)} more (see 'git status')") + print("The new project is made from the last commit, so these will not be in it.") + if not confirm("Continue anyway?"): + print("Stopped. Nothing was made. Commit or stash the changes, then run new-project again.") + return 1 + + return work(repo_path ,project_name ,remotes) if __name__ == "__main__": sys.exit(CLI()) diff --git a/administrator/tool/skeleton_diff b/administrator/tool/skeleton_diff new file mode 100755 index 0000000..973c4ae --- /dev/null +++ b/administrator/tool/skeleton_diff @@ -0,0 +1,216 @@ +#!/usr/bin/env python3 +# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*- + +import sys ,os ,filecmp ,stat ,subprocess +from pathlib import Path + +def get_project_name(repo_path: Path) -> str: + return repo_path.resolve().name + +def get_git_branch(repo_path: Path) -> str: + try: + result = subprocess.run( + ["git" ,"-C" ,str(repo_path) ,"branch" ,"--show-current"], + capture_output=True ,text=True ,check=True + ) + branch = result.stdout.strip() + return branch if branch else "unknown_branch" + except (subprocess.CalledProcessError ,FileNotFoundError): + return "unknown_branch" + +def format_perms(mode: int) -> str: + # Extract the 9-character permission string (e.g., rwxr-xr-x), dropping the leading file type char + return stat.filemode(mode)[1:] + +def CLI(argv=None) -> int: + if argv is None: + argv = sys.argv[1:] + + if len(argv) != 2: + print("Usage: skeleton_diff ") + return 1 + + harmony_dir = Path(argv[0]).resolve() + project_dir = Path(argv[1]).resolve() + + if not harmony_dir.is_dir() or not project_dir.is_dir(): + print("Error: Both arguments must be valid directories." ,file=sys.stderr) + return 1 + + project_name = get_project_name(project_dir) + harmony_branch = get_git_branch(harmony_dir) + project_branch = get_git_branch(project_dir) + + # Data structures for the output sections + opus_warnings = [] + missing_files = [] + + # Time-based modification buckets + mod_harmony_newer = [] + mod_same_time = [] + mod_project_newer = [] + + perms_only_changes = [] + added_files = [] + + # --- Section 1: Opus File Structural Audit --- + project_opus = f"0pus_{project_name}" + harmony_opus = "0pus_Harmony" + found_opus = False + + for TM_item in os.listdir(project_dir): + if TM_item.startswith("0pus_") and os.path.isfile(project_dir / TM_item): + if TM_item == project_opus: + found_opus = True + else: + opus_warnings.append(f"Incorrectly named Opus file found: '{TM_item}'. Expected '{project_opus}'.") + + if not found_opus: + opus_warnings.append(f"Expected Opus file '{project_opus}' is MISSING from project root.") + + # Semantic boundaries: isolate the skeleton by ignoring user zones + IGNORED_DIRS = {".git" ,"scratchpad" ,"consumer" ,"authored" ,"__pycache__"} + IGNORED_EXTS = {".pyc" ,".pyo" ,".swp" ,".bak"} + + def is_ignored(path_obj: Path) -> bool: + if path_obj.name in IGNORED_DIRS: return True + if path_obj.suffix in IGNORED_EXTS: return True + if path_obj.name.endswith("~"): return True + return False + + harmony_files = set() + for TM_root ,TM_dirs ,TM_files in os.walk(harmony_dir): + TM_dirs[:] = [TM_d for TM_d in TM_dirs if not is_ignored(Path(TM_d))] + for TM_f in TM_files: + f_path = Path(TM_root) / TM_f + if not is_ignored(f_path): + rel_path = f_path.relative_to(harmony_dir) + harmony_files.add(str(rel_path)) + + project_files = set() + for TM_root ,TM_dirs ,TM_files in os.walk(project_dir): + TM_dirs[:] = [TM_d for TM_d in TM_dirs if not is_ignored(Path(TM_d))] + for TM_f in TM_files: + f_path = Path(TM_root) / TM_f + if not is_ignored(f_path): + rel_path = f_path.relative_to(project_dir) + project_files.add(str(rel_path)) + + # Align the Opus file names for comparison + if project_opus in project_files: + project_files.remove(project_opus) + project_files.add(harmony_opus) + else: + for TM_item in list(project_files): + if TM_item.startswith("0pus_") and "/" not in TM_item: + project_files.remove(TM_item) + + all_files = sorted(list(harmony_files | project_files)) + + for TM_rel in all_files: + if TM_rel not in project_files: + if TM_rel == harmony_opus and not found_opus: + continue + missing_files.append(TM_rel) + elif TM_rel not in harmony_files: + added_files.append(TM_rel) + else: + h_abs = harmony_dir / TM_rel + p_abs = project_dir / TM_rel + + if TM_rel == harmony_opus: + p_abs = project_dir / project_opus + + if p_abs.exists(): + h_stat = h_abs.stat() + p_stat = p_abs.stat() + + h_perms = format_perms(h_stat.st_mode) + p_perms = format_perms(p_stat.st_mode) + perms_differ = (h_perms != p_perms) + + display_str = f"({h_perms}, {p_perms}) {TM_rel}" if perms_differ else TM_rel + content_differs = not filecmp.cmp(h_abs ,p_abs ,shallow=False) + + if content_differs: + h_time = h_stat.st_mtime + p_time = p_stat.st_mtime + + if h_time > p_time: + mod_harmony_newer.append(display_str) + elif h_time < p_time: + mod_project_newer.append(display_str) + else: + mod_same_time.append(display_str) + elif perms_differ: + perms_only_changes.append(display_str) + + # --- Output Generation --- + print("Skeleton Audit:") + print(f" Harmony {harmony_branch}") + print(" vs") + print(f" {project_name} {project_branch}") + print("=" * 60) + + print("\n1. 0pus File Audit") + print("-" * 20) + if not opus_warnings: + print(" [OK] Opus file is correctly named and present.") + else: + for TM_w in opus_warnings: + print(f" [WARNING] {TM_w}") + + print("\n2. Missing from Project (In Harmony, missing locally)") + print("-" * 20) + if not missing_files: + print(" [OK] No Harmony skeleton files are missing.") + else: + for TM_f in missing_files: + print(f" - {TM_f}") + + print("\n3. Modified Skeleton Files (Diverged from Harmony)") + print("-" * 20) + if not (mod_harmony_newer or mod_same_time or mod_project_newer): + print(" [OK] No shared skeleton files have been modified.") + else: + if mod_harmony_newer: + print(" [Harmony newer]") + for TM_f in mod_harmony_newer: + print(f" ~ {TM_f}") + if mod_same_time: + print(" [Same modification time]") + for TM_f in mod_same_time: + print(f" ~ {TM_f}") + if mod_project_newer: + print(" [Project newer]") + for TM_f in mod_project_newer: + print(f" ~ {TM_f}") + + print("\n4. Permissions Only Changes") + print("-" * 20) + if not perms_only_changes: + print(" [OK] No permission-only changes detected.") + else: + for TM_f in perms_only_changes: + print(f" * {TM_f}") + + print("\n5. Project-Specific Files (Not in Harmony)") + print("-" * 20) + if not added_files: + print(" [INFO] No additional files outside the standard skeleton.") + else: + for TM_f in added_files: + print(f" + {TM_f}") + + print("\n" + "=" * 60) + drift_count = len(opus_warnings) + len(missing_files) + len(mod_harmony_newer) + len(mod_same_time) + len(mod_project_newer) + len(perms_only_changes) + if drift_count == 0: + print("Result: Skeleton is perfectly synchronized with Harmony.") + else: + print(f"Total Structural Drift (Sections 1-4): {drift_count} issues.") + + return 0 + +if __name__ == "__main__": + sys.exit(CLI()) + diff --git a/consumer/.gitignore b/consumer/.gitignore new file mode 100644 index 0000000..76bedae --- /dev/null +++ b/consumer/.gitignore @@ -0,0 +1,5 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore + diff --git a/shared/linked-project/.gitkeep b/developer/authored/.gitkeep similarity index 100% rename from shared/linked-project/.gitkeep rename to developer/authored/.gitkeep diff --git a/administrator/document/setup.js b/developer/authored/deprecated/administrator/document/setup.js similarity index 100% rename from administrator/document/setup.js rename to developer/authored/deprecated/administrator/document/setup.js diff --git a/developer/DNS/.gitignore b/developer/authored/deprecated/developer/DNS/.gitignore similarity index 100% rename from developer/DNS/.gitignore rename to developer/authored/deprecated/developer/DNS/.gitignore diff --git a/developer/DNS/10-block-IPv6.nft b/developer/authored/deprecated/developer/DNS/10-block-IPv6.nft similarity index 100% rename from developer/DNS/10-block-IPv6.nft rename to developer/authored/deprecated/developer/DNS/10-block-IPv6.nft diff --git a/developer/DNS/20-SUBU-ports.nft b/developer/authored/deprecated/developer/DNS/20-SUBU-ports.nft similarity index 100% rename from developer/DNS/20-SUBU-ports.nft rename to developer/authored/deprecated/developer/DNS/20-SUBU-ports.nft diff --git a/developer/DNS/Man_In_Grey_input_acceptance.py b/developer/authored/deprecated/developer/DNS/Man_In_Grey_input_acceptance.py similarity index 100% rename from developer/DNS/Man_In_Grey_input_acceptance.py rename to developer/authored/deprecated/developer/DNS/Man_In_Grey_input_acceptance.py diff --git a/developer/DNS/README.org b/developer/authored/deprecated/developer/DNS/README.org similarity index 100% rename from developer/DNS/README.org rename to developer/authored/deprecated/developer/DNS/README.org diff --git a/developer/DNS/doc_howto_install.org b/developer/authored/deprecated/developer/DNS/doc_howto_install.org similarity index 100% rename from developer/DNS/doc_howto_install.org rename to developer/authored/deprecated/developer/DNS/doc_howto_install.org diff --git a/developer/DNS/stage-US/10-block-IPv6.nft.py b/developer/authored/deprecated/developer/DNS/stage-US/10-block-IPv6.nft.py similarity index 100% rename from developer/DNS/stage-US/10-block-IPv6.nft.py rename to developer/authored/deprecated/developer/DNS/stage-US/10-block-IPv6.nft.py diff --git a/developer/DNS/stage-US/20-SUBU-ports-US.nft.py b/developer/authored/deprecated/developer/DNS/stage-US/20-SUBU-ports-US.nft.py similarity index 100% rename from developer/DNS/stage-US/20-SUBU-ports-US.nft.py rename to developer/authored/deprecated/developer/DNS/stage-US/20-SUBU-ports-US.nft.py diff --git a/developer/DNS/stage-US/30-unbound-US.conf.py b/developer/authored/deprecated/developer/DNS/stage-US/30-unbound-US.conf.py similarity index 100% rename from developer/DNS/stage-US/30-unbound-US.conf.py rename to developer/authored/deprecated/developer/DNS/stage-US/30-unbound-US.conf.py diff --git a/developer/DNS/stage-US/50-wg-policy-US.service.py b/developer/authored/deprecated/developer/DNS/stage-US/50-wg-policy-US.service.py similarity index 100% rename from developer/DNS/stage-US/50-wg-policy-US.service.py rename to developer/authored/deprecated/developer/DNS/stage-US/50-wg-policy-US.service.py diff --git a/developer/DNS/stage-US/51-wg-policy-US.sh.py b/developer/authored/deprecated/developer/DNS/stage-US/51-wg-policy-US.sh.py similarity index 100% rename from developer/DNS/stage-US/51-wg-policy-US.sh.py rename to developer/authored/deprecated/developer/DNS/stage-US/51-wg-policy-US.sh.py diff --git a/developer/DNS/stage_0/30-dnsredir.nft b/developer/authored/deprecated/developer/DNS/stage_0/30-dnsredir.nft similarity index 100% rename from developer/DNS/stage_0/30-dnsredir.nft rename to developer/authored/deprecated/developer/DNS/stage_0/30-dnsredir.nft diff --git a/developer/DNS/stage_0/unbound-US.conf b/developer/authored/deprecated/developer/DNS/stage_0/unbound-US.conf similarity index 100% rename from developer/DNS/stage_0/unbound-US.conf rename to developer/authored/deprecated/developer/DNS/stage_0/unbound-US.conf diff --git a/developer/DNS/stage_0/unbound-x6.conf b/developer/authored/deprecated/developer/DNS/stage_0/unbound-x6.conf similarity index 100% rename from developer/DNS/stage_0/unbound-x6.conf rename to developer/authored/deprecated/developer/DNS/stage_0/unbound-x6.conf diff --git a/developer/DNS/stage_0/unbound@.service b/developer/authored/deprecated/developer/DNS/stage_0/unbound@.service similarity index 100% rename from developer/DNS/stage_0/unbound@.service rename to developer/authored/deprecated/developer/DNS/stage_0/unbound@.service diff --git a/developer/DNS/unbound_US.py b/developer/authored/deprecated/developer/DNS/unbound_US.py similarity index 100% rename from developer/DNS/unbound_US.py rename to developer/authored/deprecated/developer/DNS/unbound_US.py diff --git a/developer/DNS/unbound_at_template.py b/developer/authored/deprecated/developer/DNS/unbound_at_template.py similarity index 100% rename from developer/DNS/unbound_at_template.py rename to developer/authored/deprecated/developer/DNS/unbound_at_template.py diff --git a/developer/DNS/unbound_x6.py b/developer/authored/deprecated/developer/DNS/unbound_x6.py similarity index 100% rename from developer/DNS/unbound_x6.py rename to developer/authored/deprecated/developer/DNS/unbound_x6.py diff --git a/developer/cc/Db.lib.c b/developer/authored/deprecated/developer/cc/Db.lib.c similarity index 100% rename from developer/cc/Db.lib.c rename to developer/authored/deprecated/developer/cc/Db.lib.c diff --git a/developer/cc/DbSubu.lib.c b/developer/authored/deprecated/developer/cc/DbSubu.lib.c similarity index 100% rename from developer/cc/DbSubu.lib.c rename to developer/authored/deprecated/developer/cc/DbSubu.lib.c diff --git a/developer/cc/Db_close.cli.c b/developer/authored/deprecated/developer/cc/Db_close.cli.c similarity index 100% rename from developer/cc/Db_close.cli.c rename to developer/authored/deprecated/developer/cc/Db_close.cli.c diff --git a/developer/cc/Hello.cli.c b/developer/authored/deprecated/developer/cc/Hello.cli.c similarity index 100% rename from developer/cc/Hello.cli.c rename to developer/authored/deprecated/developer/cc/Hello.cli.c diff --git a/developer/cc/Hello.lib.c b/developer/authored/deprecated/developer/cc/Hello.lib.c similarity index 100% rename from developer/cc/Hello.lib.c rename to developer/authored/deprecated/developer/cc/Hello.lib.c diff --git a/developer/cc/README.org b/developer/authored/deprecated/developer/cc/README.org similarity index 100% rename from developer/cc/README.org rename to developer/authored/deprecated/developer/cc/README.org diff --git a/developer/cc/Server.cli.c b/developer/authored/deprecated/developer/cc/Server.cli.c similarity index 100% rename from developer/cc/Server.cli.c rename to developer/authored/deprecated/developer/cc/Server.cli.c diff --git a/developer/cc/Server.lib.c b/developer/authored/deprecated/developer/cc/Server.lib.c similarity index 100% rename from developer/cc/Server.lib.c rename to developer/authored/deprecated/developer/cc/Server.lib.c diff --git a/developer/cc/db_add_user.cli.c b/developer/authored/deprecated/developer/cc/db_add_user.cli.c similarity index 100% rename from developer/cc/db_add_user.cli.c rename to developer/authored/deprecated/developer/cc/db_add_user.cli.c diff --git a/developer/cc/db_delete_user.cli.c b/developer/authored/deprecated/developer/cc/db_delete_user.cli.c similarity index 100% rename from developer/cc/db_delete_user.cli.c rename to developer/authored/deprecated/developer/cc/db_delete_user.cli.c diff --git a/developer/cc/db_log_event.cli.c b/developer/authored/deprecated/developer/cc/db_log_event.cli.c similarity index 100% rename from developer/cc/db_log_event.cli.c rename to developer/authored/deprecated/developer/cc/db_log_event.cli.c diff --git a/developer/cc/db_open.cli.c b/developer/authored/deprecated/developer/cc/db_open.cli.c similarity index 100% rename from developer/cc/db_open.cli.c rename to developer/authored/deprecated/developer/cc/db_open.cli.c diff --git a/developer/cc/db_validate_schema.cli.c b/developer/authored/deprecated/developer/cc/db_validate_schema.cli.c similarity index 100% rename from developer/cc/db_validate_schema.cli.c rename to developer/authored/deprecated/developer/cc/db_validate_schema.cli.c diff --git a/developer/cc/scratchpad/.gitignore b/developer/authored/deprecated/developer/cc/scratchpad/.gitignore similarity index 100% rename from developer/cc/scratchpad/.gitignore rename to developer/authored/deprecated/developer/cc/scratchpad/.gitignore diff --git a/developer/cc/sqlite/schema.sql b/developer/authored/deprecated/developer/cc/sqlite/schema.sql similarity index 100% rename from developer/cc/sqlite/schema.sql rename to developer/authored/deprecated/developer/cc/sqlite/schema.sql diff --git a/developer/deprecated/.gitignore b/developer/authored/deprecated/developer/deprecated/.gitignore similarity index 100% rename from developer/deprecated/.gitignore rename to developer/authored/deprecated/developer/deprecated/.gitignore diff --git a/developer/device_management/bestow_audio.sh b/developer/authored/deprecated/developer/device_management/bestow_audio.sh similarity index 100% rename from developer/device_management/bestow_audio.sh rename to developer/authored/deprecated/developer/device_management/bestow_audio.sh diff --git a/developer/login/logout_subu.sh b/developer/authored/deprecated/developer/login/logout_subu.sh similarity index 100% rename from developer/login/logout_subu.sh rename to developer/authored/deprecated/developer/login/logout_subu.sh diff --git a/developer/login/subu_login b/developer/authored/deprecated/developer/login/subu_login similarity index 100% rename from developer/login/subu_login rename to developer/authored/deprecated/developer/login/subu_login diff --git a/developer/manager/CLI/CLI.py b/developer/authored/deprecated/developer/manager/CLI/CLI.py similarity index 100% rename from developer/manager/CLI/CLI.py rename to developer/authored/deprecated/developer/manager/CLI/CLI.py diff --git a/developer/manager/CLI/dispatch.py b/developer/authored/deprecated/developer/manager/CLI/dispatch.py similarity index 100% rename from developer/manager/CLI/dispatch.py rename to developer/authored/deprecated/developer/manager/CLI/dispatch.py diff --git a/developer/manager/CLI/domain/device.py b/developer/authored/deprecated/developer/manager/CLI/domain/device.py similarity index 100% rename from developer/manager/CLI/domain/device.py rename to developer/authored/deprecated/developer/manager/CLI/domain/device.py diff --git a/developer/manager/CLI/domain/exec.py b/developer/authored/deprecated/developer/manager/CLI/domain/exec.py similarity index 100% rename from developer/manager/CLI/domain/exec.py rename to developer/authored/deprecated/developer/manager/CLI/domain/exec.py diff --git a/developer/manager/CLI/domain/network.py b/developer/authored/deprecated/developer/manager/CLI/domain/network.py similarity index 100% rename from developer/manager/CLI/domain/network.py rename to developer/authored/deprecated/developer/manager/CLI/domain/network.py diff --git a/developer/manager/CLI/domain/options.py b/developer/authored/deprecated/developer/manager/CLI/domain/options.py similarity index 100% rename from developer/manager/CLI/domain/options.py rename to developer/authored/deprecated/developer/manager/CLI/domain/options.py diff --git a/developer/manager/CLI/domain/subu.py b/developer/authored/deprecated/developer/manager/CLI/domain/subu.py similarity index 100% rename from developer/manager/CLI/domain/subu.py rename to developer/authored/deprecated/developer/manager/CLI/domain/subu.py diff --git a/developer/manager/CLI/domain/wg.py b/developer/authored/deprecated/developer/manager/CLI/domain/wg.py similarity index 100% rename from developer/manager/CLI/domain/wg.py rename to developer/authored/deprecated/developer/manager/CLI/domain/wg.py diff --git a/developer/manager/CLI/env.py b/developer/authored/deprecated/developer/manager/CLI/env.py similarity index 100% rename from developer/manager/CLI/env.py rename to developer/authored/deprecated/developer/manager/CLI/env.py diff --git a/developer/manager/CLI/infrastructure/bpf.py b/developer/authored/deprecated/developer/manager/CLI/infrastructure/bpf.py similarity index 100% rename from developer/manager/CLI/infrastructure/bpf.py rename to developer/authored/deprecated/developer/manager/CLI/infrastructure/bpf.py diff --git a/developer/manager/CLI/infrastructure/bpf_force_egress.c b/developer/authored/deprecated/developer/manager/CLI/infrastructure/bpf_force_egress.c similarity index 100% rename from developer/manager/CLI/infrastructure/bpf_force_egress.c rename to developer/authored/deprecated/developer/manager/CLI/infrastructure/bpf_force_egress.c diff --git a/developer/manager/CLI/infrastructure/bpf_worker.py b/developer/authored/deprecated/developer/manager/CLI/infrastructure/bpf_worker.py similarity index 100% rename from developer/manager/CLI/infrastructure/bpf_worker.py rename to developer/authored/deprecated/developer/manager/CLI/infrastructure/bpf_worker.py diff --git a/developer/manager/CLI/infrastructure/db.py b/developer/authored/deprecated/developer/manager/CLI/infrastructure/db.py similarity index 100% rename from developer/manager/CLI/infrastructure/db.py rename to developer/authored/deprecated/developer/manager/CLI/infrastructure/db.py diff --git a/developer/manager/CLI/infrastructure/device.py b/developer/authored/deprecated/developer/manager/CLI/infrastructure/device.py similarity index 100% rename from developer/manager/CLI/infrastructure/device.py rename to developer/authored/deprecated/developer/manager/CLI/infrastructure/device.py diff --git a/developer/manager/CLI/infrastructure/options_store.py b/developer/authored/deprecated/developer/manager/CLI/infrastructure/options_store.py similarity index 100% rename from developer/manager/CLI/infrastructure/options_store.py rename to developer/authored/deprecated/developer/manager/CLI/infrastructure/options_store.py diff --git a/developer/manager/CLI/infrastructure/schema.sql b/developer/authored/deprecated/developer/manager/CLI/infrastructure/schema.sql similarity index 100% rename from developer/manager/CLI/infrastructure/schema.sql rename to developer/authored/deprecated/developer/manager/CLI/infrastructure/schema.sql diff --git a/developer/manager/CLI/infrastructure/unix.py b/developer/authored/deprecated/developer/manager/CLI/infrastructure/unix.py similarity index 100% rename from developer/manager/CLI/infrastructure/unix.py rename to developer/authored/deprecated/developer/manager/CLI/infrastructure/unix.py diff --git a/developer/manager/CLI/text.py b/developer/authored/deprecated/developer/manager/CLI/text.py similarity index 100% rename from developer/manager/CLI/text.py rename to developer/authored/deprecated/developer/manager/CLI/text.py diff --git a/developer/manager/install b/developer/authored/deprecated/developer/manager/install similarity index 100% rename from developer/manager/install rename to developer/authored/deprecated/developer/manager/install diff --git a/developer/manager/systemd/boot_attach.service b/developer/authored/deprecated/developer/manager/systemd/boot_attach.service similarity index 100% rename from developer/manager/systemd/boot_attach.service rename to developer/authored/deprecated/developer/manager/systemd/boot_attach.service diff --git a/developer/manager/systemd/subu_resume.service b/developer/authored/deprecated/developer/manager/systemd/subu_resume.service similarity index 100% rename from developer/manager/systemd/subu_resume.service rename to developer/authored/deprecated/developer/manager/systemd/subu_resume.service diff --git a/developer/manager/usr_local_bin/boot_attach b/developer/authored/deprecated/developer/manager/usr_local_bin/boot_attach similarity index 100% rename from developer/manager/usr_local_bin/boot_attach rename to developer/authored/deprecated/developer/manager/usr_local_bin/boot_attach diff --git a/developer/mount/README.org b/developer/authored/deprecated/developer/mount/README.org similarity index 100% rename from developer/mount/README.org rename to developer/authored/deprecated/developer/mount/README.org diff --git a/developer/mount/device_mapname__open_mount.sh b/developer/authored/deprecated/developer/mount/device_mapname__open_mount.sh similarity index 100% rename from developer/mount/device_mapname__open_mount.sh rename to developer/authored/deprecated/developer/mount/device_mapname__open_mount.sh diff --git a/developer/mount/device_umount.sh b/developer/authored/deprecated/developer/mount/device_umount.sh similarity index 100% rename from developer/mount/device_umount.sh rename to developer/authored/deprecated/developer/mount/device_umount.sh diff --git a/developer/mount/disable_linger.sh b/developer/authored/deprecated/developer/mount/disable_linger.sh similarity index 100% rename from developer/mount/disable_linger.sh rename to developer/authored/deprecated/developer/mount/disable_linger.sh diff --git a/developer/mount/logout_subu.sh b/developer/authored/deprecated/developer/mount/logout_subu.sh similarity index 100% rename from developer/mount/logout_subu.sh rename to developer/authored/deprecated/developer/mount/logout_subu.sh diff --git a/developer/mount/mapname__mounted_masu_list.sh b/developer/authored/deprecated/developer/mount/mapname__mounted_masu_list.sh similarity index 100% rename from developer/mount/mapname__mounted_masu_list.sh rename to developer/authored/deprecated/developer/mount/mapname__mounted_masu_list.sh diff --git a/developer/mount/mapname_masu__to_home.sh b/developer/authored/deprecated/developer/mount/mapname_masu__to_home.sh similarity index 100% rename from developer/mount/mapname_masu__to_home.sh rename to developer/authored/deprecated/developer/mount/mapname_masu__to_home.sh diff --git a/developer/mount/masu__map_own_all.sh b/developer/authored/deprecated/developer/mount/masu__map_own_all.sh similarity index 100% rename from developer/mount/masu__map_own_all.sh rename to developer/authored/deprecated/developer/mount/masu__map_own_all.sh diff --git a/developer/mount/masu__subu_data_dir_list.sh b/developer/authored/deprecated/developer/mount/masu__subu_data_dir_list.sh similarity index 100% rename from developer/mount/masu__subu_data_dir_list.sh rename to developer/authored/deprecated/developer/mount/masu__subu_data_dir_list.sh diff --git a/developer/mount/masu__subu_dir_list.sh b/developer/authored/deprecated/developer/mount/masu__subu_dir_list.sh similarity index 100% rename from developer/mount/masu__subu_dir_list.sh rename to developer/authored/deprecated/developer/mount/masu__subu_dir_list.sh diff --git a/developer/mount/masu_subu__map_own.sh b/developer/authored/deprecated/developer/mount/masu_subu__map_own.sh similarity index 100% rename from developer/mount/masu_subu__map_own.sh rename to developer/authored/deprecated/developer/mount/masu_subu__map_own.sh diff --git a/developer/mount/masu_subu__map_own_orig.sh b/developer/authored/deprecated/developer/mount/masu_subu__map_own_orig.sh similarity index 100% rename from developer/mount/masu_subu__map_own_orig.sh rename to developer/authored/deprecated/developer/mount/masu_subu__map_own_orig.sh diff --git a/developer/mount/masu_subu__uid.sh b/developer/authored/deprecated/developer/mount/masu_subu__uid.sh similarity index 100% rename from developer/mount/masu_subu__uid.sh rename to developer/authored/deprecated/developer/mount/masu_subu__uid.sh diff --git a/developer/mount/mount_pt_list.sh b/developer/authored/deprecated/developer/mount/mount_pt_list.sh similarity index 100% rename from developer/mount/mount_pt_list.sh rename to developer/authored/deprecated/developer/mount/mount_pt_list.sh diff --git a/developer/tool/clean b/developer/authored/deprecated/developer/tool/clean similarity index 100% rename from developer/tool/clean rename to developer/authored/deprecated/developer/tool/clean diff --git a/developer/tool/env b/developer/authored/deprecated/developer/tool/env similarity index 100% rename from developer/tool/env rename to developer/authored/deprecated/developer/tool/env diff --git a/developer/tool/make b/developer/authored/deprecated/developer/tool/make similarity index 100% rename from developer/tool/make rename to developer/authored/deprecated/developer/tool/make diff --git a/developer/tool/makefile b/developer/authored/deprecated/developer/tool/makefile similarity index 100% rename from developer/tool/makefile rename to developer/authored/deprecated/developer/tool/makefile diff --git a/developer/tool/mockup_update b/developer/authored/deprecated/developer/tool/mockup_update similarity index 100% rename from developer/tool/mockup_update rename to developer/authored/deprecated/developer/tool/mockup_update diff --git a/developer/tool/release b/developer/authored/deprecated/developer/tool/release similarity index 100% rename from developer/tool/release rename to developer/authored/deprecated/developer/tool/release diff --git a/developer/tool/rm_pycache b/developer/authored/deprecated/developer/tool/rm_pycache similarity index 100% rename from developer/tool/rm_pycache rename to developer/authored/deprecated/developer/tool/rm_pycache diff --git a/developer/tunnel-client/.gitignore b/developer/authored/deprecated/developer/tunnel-client/.gitignore similarity index 100% rename from developer/tunnel-client/.gitignore rename to developer/authored/deprecated/developer/tunnel-client/.gitignore diff --git a/developer/tunnel-client/db/.gitignore b/developer/authored/deprecated/developer/tunnel-client/db/.gitignore similarity index 100% rename from developer/tunnel-client/db/.gitignore rename to developer/authored/deprecated/developer/tunnel-client/db/.gitignore diff --git a/developer/tunnel-client/db_bind_user_to_iface.py b/developer/authored/deprecated/developer/tunnel-client/db_bind_user_to_iface.py similarity index 100% rename from developer/tunnel-client/db_bind_user_to_iface.py rename to developer/authored/deprecated/developer/tunnel-client/db_bind_user_to_iface.py diff --git a/developer/tunnel-client/db_checks.py b/developer/authored/deprecated/developer/tunnel-client/db_checks.py similarity index 100% rename from developer/tunnel-client/db_checks.py rename to developer/authored/deprecated/developer/tunnel-client/db_checks.py diff --git a/developer/tunnel-client/db_init_StanleyPark.py b/developer/authored/deprecated/developer/tunnel-client/db_init_StanleyPark.py similarity index 100% rename from developer/tunnel-client/db_init_StanleyPark.py rename to developer/authored/deprecated/developer/tunnel-client/db_init_StanleyPark.py diff --git a/developer/tunnel-client/db_init_iface.py b/developer/authored/deprecated/developer/tunnel-client/db_init_iface.py similarity index 100% rename from developer/tunnel-client/db_init_iface.py rename to developer/authored/deprecated/developer/tunnel-client/db_init_iface.py diff --git a/developer/tunnel-client/db_init_iface_US.py b/developer/authored/deprecated/developer/tunnel-client/db_init_iface_US.py similarity index 100% rename from developer/tunnel-client/db_init_iface_US.py rename to developer/authored/deprecated/developer/tunnel-client/db_init_iface_US.py diff --git a/developer/tunnel-client/db_init_iface_x6.py b/developer/authored/deprecated/developer/tunnel-client/db_init_iface_x6.py similarity index 100% rename from developer/tunnel-client/db_init_iface_x6.py rename to developer/authored/deprecated/developer/tunnel-client/db_init_iface_x6.py diff --git a/developer/tunnel-client/db_init_ip_iface_addr_assign.py b/developer/authored/deprecated/developer/tunnel-client/db_init_ip_iface_addr_assign.py similarity index 100% rename from developer/tunnel-client/db_init_ip_iface_addr_assign.py rename to developer/authored/deprecated/developer/tunnel-client/db_init_ip_iface_addr_assign.py diff --git a/developer/tunnel-client/db_init_ip_table_registration.py b/developer/authored/deprecated/developer/tunnel-client/db_init_ip_table_registration.py similarity index 100% rename from developer/tunnel-client/db_init_ip_table_registration.py rename to developer/authored/deprecated/developer/tunnel-client/db_init_ip_table_registration.py diff --git a/developer/tunnel-client/db_init_route_defaults.py b/developer/authored/deprecated/developer/tunnel-client/db_init_route_defaults.py similarity index 100% rename from developer/tunnel-client/db_init_route_defaults.py rename to developer/authored/deprecated/developer/tunnel-client/db_init_route_defaults.py diff --git a/developer/tunnel-client/db_init_server_US.py b/developer/authored/deprecated/developer/tunnel-client/db_init_server_US.py similarity index 100% rename from developer/tunnel-client/db_init_server_US.py rename to developer/authored/deprecated/developer/tunnel-client/db_init_server_US.py diff --git a/developer/tunnel-client/db_init_server_incommon.py b/developer/authored/deprecated/developer/tunnel-client/db_init_server_incommon.py similarity index 100% rename from developer/tunnel-client/db_init_server_incommon.py rename to developer/authored/deprecated/developer/tunnel-client/db_init_server_incommon.py diff --git a/developer/tunnel-client/db_init_server_x6.py b/developer/authored/deprecated/developer/tunnel-client/db_init_server_x6.py similarity index 100% rename from developer/tunnel-client/db_init_server_x6.py rename to developer/authored/deprecated/developer/tunnel-client/db_init_server_x6.py diff --git a/developer/tunnel-client/db_schema.sql b/developer/authored/deprecated/developer/tunnel-client/db_schema.sql similarity index 100% rename from developer/tunnel-client/db_schema.sql rename to developer/authored/deprecated/developer/tunnel-client/db_schema.sql diff --git a/developer/tunnel-client/db_schema_load.sh b/developer/authored/deprecated/developer/tunnel-client/db_schema_load.sh similarity index 100% rename from developer/tunnel-client/db_schema_load.sh rename to developer/authored/deprecated/developer/tunnel-client/db_schema_load.sh diff --git a/developer/tunnel-client/db_wipe.py b/developer/authored/deprecated/developer/tunnel-client/db_wipe.py similarity index 100% rename from developer/tunnel-client/db_wipe.py rename to developer/authored/deprecated/developer/tunnel-client/db_wipe.py diff --git a/developer/tunnel-client/deploy_StanleyPark.py b/developer/authored/deprecated/developer/tunnel-client/deploy_StanleyPark.py similarity index 100% rename from developer/tunnel-client/deploy_StanleyPark.py rename to developer/authored/deprecated/developer/tunnel-client/deploy_StanleyPark.py diff --git a/developer/tunnel-client/deprecated/.gitignore b/developer/authored/deprecated/developer/tunnel-client/deprecated/.gitignore similarity index 100% rename from developer/tunnel-client/deprecated/.gitignore rename to developer/authored/deprecated/developer/tunnel-client/deprecated/.gitignore diff --git a/developer/tunnel-client/doc_IP_terminaology.org b/developer/authored/deprecated/developer/tunnel-client/doc_IP_terminaology.org similarity index 100% rename from developer/tunnel-client/doc_IP_terminaology.org rename to developer/authored/deprecated/developer/tunnel-client/doc_IP_terminaology.org diff --git a/developer/tunnel-client/doc_StanleyPark.org b/developer/authored/deprecated/developer/tunnel-client/doc_StanleyPark.org similarity index 100% rename from developer/tunnel-client/doc_StanleyPark.org rename to developer/authored/deprecated/developer/tunnel-client/doc_StanleyPark.org diff --git a/developer/tunnel-client/doc_config.org b/developer/authored/deprecated/developer/tunnel-client/doc_config.org similarity index 100% rename from developer/tunnel-client/doc_config.org rename to developer/authored/deprecated/developer/tunnel-client/doc_config.org diff --git a/developer/tunnel-client/doc_keys.org b/developer/authored/deprecated/developer/tunnel-client/doc_keys.org similarity index 100% rename from developer/tunnel-client/doc_keys.org rename to developer/authored/deprecated/developer/tunnel-client/doc_keys.org diff --git a/developer/tunnel-client/doc_stage_progs.org b/developer/authored/deprecated/developer/tunnel-client/doc_stage_progs.org similarity index 100% rename from developer/tunnel-client/doc_stage_progs.org rename to developer/authored/deprecated/developer/tunnel-client/doc_stage_progs.org diff --git a/developer/tunnel-client/iface_down.py b/developer/authored/deprecated/developer/tunnel-client/iface_down.py similarity index 100% rename from developer/tunnel-client/iface_down.py rename to developer/authored/deprecated/developer/tunnel-client/iface_down.py diff --git a/developer/tunnel-client/iface_status.py b/developer/authored/deprecated/developer/tunnel-client/iface_status.py similarity index 100% rename from developer/tunnel-client/iface_status.py rename to developer/authored/deprecated/developer/tunnel-client/iface_status.py diff --git a/developer/tunnel-client/iface_up.sh b/developer/authored/deprecated/developer/tunnel-client/iface_up.sh similarity index 100% rename from developer/tunnel-client/iface_up.sh rename to developer/authored/deprecated/developer/tunnel-client/iface_up.sh diff --git a/developer/tunnel-client/incommon.py b/developer/authored/deprecated/developer/tunnel-client/incommon.py similarity index 100% rename from developer/tunnel-client/incommon.py rename to developer/authored/deprecated/developer/tunnel-client/incommon.py diff --git a/developer/tunnel-client/inspect.sh b/developer/authored/deprecated/developer/tunnel-client/inspect.sh similarity index 100% rename from developer/tunnel-client/inspect.sh rename to developer/authored/deprecated/developer/tunnel-client/inspect.sh diff --git a/developer/tunnel-client/inspect_1.py b/developer/authored/deprecated/developer/tunnel-client/inspect_1.py similarity index 100% rename from developer/tunnel-client/inspect_1.py rename to developer/authored/deprecated/developer/tunnel-client/inspect_1.py diff --git a/developer/tunnel-client/inspect_client_public_key.py b/developer/authored/deprecated/developer/tunnel-client/inspect_client_public_key.py similarity index 100% rename from developer/tunnel-client/inspect_client_public_key.py rename to developer/authored/deprecated/developer/tunnel-client/inspect_client_public_key.py diff --git a/developer/tunnel-client/install_staged_tree.py b/developer/authored/deprecated/developer/tunnel-client/install_staged_tree.py similarity index 100% rename from developer/tunnel-client/install_staged_tree.py rename to developer/authored/deprecated/developer/tunnel-client/install_staged_tree.py diff --git a/developer/tunnel-client/key/.gitignore b/developer/authored/deprecated/developer/tunnel-client/key/.gitignore similarity index 100% rename from developer/tunnel-client/key/.gitignore rename to developer/authored/deprecated/developer/tunnel-client/key/.gitignore diff --git a/developer/tunnel-client/key_client_generate.py b/developer/authored/deprecated/developer/tunnel-client/key_client_generate.py similarity index 100% rename from developer/tunnel-client/key_client_generate.py rename to developer/authored/deprecated/developer/tunnel-client/key_client_generate.py diff --git a/developer/tunnel-client/key_server_set.py b/developer/authored/deprecated/developer/tunnel-client/key_server_set.py similarity index 100% rename from developer/tunnel-client/key_server_set.py rename to developer/authored/deprecated/developer/tunnel-client/key_server_set.py diff --git a/developer/tunnel-client/ls_iface.py b/developer/authored/deprecated/developer/tunnel-client/ls_iface.py similarity index 100% rename from developer/tunnel-client/ls_iface.py rename to developer/authored/deprecated/developer/tunnel-client/ls_iface.py diff --git a/developer/tunnel-client/ls_key.py b/developer/authored/deprecated/developer/tunnel-client/ls_key.py similarity index 100% rename from developer/tunnel-client/ls_key.py rename to developer/authored/deprecated/developer/tunnel-client/ls_key.py diff --git a/developer/tunnel-client/ls_server.py b/developer/authored/deprecated/developer/tunnel-client/ls_server.py similarity index 100% rename from developer/tunnel-client/ls_server.py rename to developer/authored/deprecated/developer/tunnel-client/ls_server.py diff --git a/developer/tunnel-client/ls_server_setting.py b/developer/authored/deprecated/developer/tunnel-client/ls_server_setting.py similarity index 100% rename from developer/tunnel-client/ls_server_setting.py rename to developer/authored/deprecated/developer/tunnel-client/ls_server_setting.py diff --git a/developer/tunnel-client/ls_servers.sh b/developer/authored/deprecated/developer/tunnel-client/ls_servers.sh similarity index 100% rename from developer/tunnel-client/ls_servers.sh rename to developer/authored/deprecated/developer/tunnel-client/ls_servers.sh diff --git a/developer/tunnel-client/ls_user.py b/developer/authored/deprecated/developer/tunnel-client/ls_user.py similarity index 100% rename from developer/tunnel-client/ls_user.py rename to developer/authored/deprecated/developer/tunnel-client/ls_user.py diff --git a/developer/tunnel-client/manual_reference.org b/developer/authored/deprecated/developer/tunnel-client/manual_reference.org similarity index 100% rename from developer/tunnel-client/manual_reference.org rename to developer/authored/deprecated/developer/tunnel-client/manual_reference.org diff --git a/developer/tunnel-client/manual_user.org b/developer/authored/deprecated/developer/tunnel-client/manual_user.org similarity index 100% rename from developer/tunnel-client/manual_user.org rename to developer/authored/deprecated/developer/tunnel-client/manual_user.org diff --git a/developer/tunnel-client/scratchpad/.gitignore b/developer/authored/deprecated/developer/tunnel-client/scratchpad/.gitignore similarity index 100% rename from developer/tunnel-client/scratchpad/.gitignore rename to developer/authored/deprecated/developer/tunnel-client/scratchpad/.gitignore diff --git a/developer/tunnel-client/stage/.gitignore b/developer/authored/deprecated/developer/tunnel-client/stage/.gitignore similarity index 100% rename from developer/tunnel-client/stage/.gitignore rename to developer/authored/deprecated/developer/tunnel-client/stage/.gitignore diff --git a/developer/tunnel-client/stage_IP_apply_script.py b/developer/authored/deprecated/developer/tunnel-client/stage_IP_apply_script.py similarity index 100% rename from developer/tunnel-client/stage_IP_apply_script.py rename to developer/authored/deprecated/developer/tunnel-client/stage_IP_apply_script.py diff --git a/developer/tunnel-client/stage_StanleyPark.py b/developer/authored/deprecated/developer/tunnel-client/stage_StanleyPark.py similarity index 100% rename from developer/tunnel-client/stage_StanleyPark.py rename to developer/authored/deprecated/developer/tunnel-client/stage_StanleyPark.py diff --git a/developer/tunnel-client/stage_client.py b/developer/authored/deprecated/developer/tunnel-client/stage_client.py similarity index 100% rename from developer/tunnel-client/stage_client.py rename to developer/authored/deprecated/developer/tunnel-client/stage_client.py diff --git a/developer/tunnel-client/stage_wg_conf.py b/developer/authored/deprecated/developer/tunnel-client/stage_wg_conf.py similarity index 100% rename from developer/tunnel-client/stage_wg_conf.py rename to developer/authored/deprecated/developer/tunnel-client/stage_wg_conf.py diff --git a/developer/tunnel-client/stage_wipe.py b/developer/authored/deprecated/developer/tunnel-client/stage_wipe.py similarity index 100% rename from developer/tunnel-client/stage_wipe.py rename to developer/authored/deprecated/developer/tunnel-client/stage_wipe.py diff --git a/developer/tunnel-client/start_iface.py b/developer/authored/deprecated/developer/tunnel-client/start_iface.py similarity index 100% rename from developer/tunnel-client/start_iface.py rename to developer/authored/deprecated/developer/tunnel-client/start_iface.py diff --git a/developer/tunnel-client/stop_clean_iface.py b/developer/authored/deprecated/developer/tunnel-client/stop_clean_iface.py similarity index 100% rename from developer/tunnel-client/stop_clean_iface.py rename to developer/authored/deprecated/developer/tunnel-client/stop_clean_iface.py diff --git a/developer/tunnel-client/todo.org b/developer/authored/deprecated/developer/tunnel-client/todo.org similarity index 100% rename from developer/tunnel-client/todo.org rename to developer/authored/deprecated/developer/tunnel-client/todo.org diff --git a/developer/tunnel-client/wg_keys_incommon.py b/developer/authored/deprecated/developer/tunnel-client/wg_keys_incommon.py similarity index 100% rename from developer/tunnel-client/wg_keys_incommon.py rename to developer/authored/deprecated/developer/tunnel-client/wg_keys_incommon.py diff --git a/developer/tunnel-server/set_client_key.sh b/developer/authored/deprecated/developer/tunnel-server/set_client_key.sh similarity index 100% rename from developer/tunnel-server/set_client_key.sh rename to developer/authored/deprecated/developer/tunnel-server/set_client_key.sh diff --git a/developer/tunnel-server/setup.sh b/developer/authored/deprecated/developer/tunnel-server/setup.sh similarity index 100% rename from developer/tunnel-server/setup.sh rename to developer/authored/deprecated/developer/tunnel-server/setup.sh diff --git a/release/manager/CLI.py b/developer/authored/deprecated/release/manager/CLI.py similarity index 100% rename from release/manager/CLI.py rename to developer/authored/deprecated/release/manager/CLI.py diff --git a/release/manager/dispatch.py b/developer/authored/deprecated/release/manager/dispatch.py similarity index 100% rename from release/manager/dispatch.py rename to developer/authored/deprecated/release/manager/dispatch.py diff --git a/release/manager/domain/exec.py b/developer/authored/deprecated/release/manager/domain/exec.py similarity index 100% rename from release/manager/domain/exec.py rename to developer/authored/deprecated/release/manager/domain/exec.py diff --git a/release/manager/domain/network.py b/developer/authored/deprecated/release/manager/domain/network.py similarity index 100% rename from release/manager/domain/network.py rename to developer/authored/deprecated/release/manager/domain/network.py diff --git a/release/manager/domain/options.py b/developer/authored/deprecated/release/manager/domain/options.py similarity index 100% rename from release/manager/domain/options.py rename to developer/authored/deprecated/release/manager/domain/options.py diff --git a/release/manager/domain/subu.py b/developer/authored/deprecated/release/manager/domain/subu.py similarity index 100% rename from release/manager/domain/subu.py rename to developer/authored/deprecated/release/manager/domain/subu.py diff --git a/release/manager/domain/wg.py b/developer/authored/deprecated/release/manager/domain/wg.py similarity index 100% rename from release/manager/domain/wg.py rename to developer/authored/deprecated/release/manager/domain/wg.py diff --git a/release/manager/env.py b/developer/authored/deprecated/release/manager/env.py similarity index 100% rename from release/manager/env.py rename to developer/authored/deprecated/release/manager/env.py diff --git a/release/manager/infrastructure/bpf.py b/developer/authored/deprecated/release/manager/infrastructure/bpf.py similarity index 100% rename from release/manager/infrastructure/bpf.py rename to developer/authored/deprecated/release/manager/infrastructure/bpf.py diff --git a/release/manager/infrastructure/bpf_force_egress.c b/developer/authored/deprecated/release/manager/infrastructure/bpf_force_egress.c similarity index 100% rename from release/manager/infrastructure/bpf_force_egress.c rename to developer/authored/deprecated/release/manager/infrastructure/bpf_force_egress.c diff --git a/release/manager/infrastructure/bpf_worker.py b/developer/authored/deprecated/release/manager/infrastructure/bpf_worker.py similarity index 100% rename from release/manager/infrastructure/bpf_worker.py rename to developer/authored/deprecated/release/manager/infrastructure/bpf_worker.py diff --git a/release/manager/infrastructure/db.py b/developer/authored/deprecated/release/manager/infrastructure/db.py similarity index 100% rename from release/manager/infrastructure/db.py rename to developer/authored/deprecated/release/manager/infrastructure/db.py diff --git a/release/manager/infrastructure/options_store.py b/developer/authored/deprecated/release/manager/infrastructure/options_store.py similarity index 100% rename from release/manager/infrastructure/options_store.py rename to developer/authored/deprecated/release/manager/infrastructure/options_store.py diff --git a/release/manager/infrastructure/schema.sql b/developer/authored/deprecated/release/manager/infrastructure/schema.sql similarity index 100% rename from release/manager/infrastructure/schema.sql rename to developer/authored/deprecated/release/manager/infrastructure/schema.sql diff --git a/release/manager/infrastructure/unix.py b/developer/authored/deprecated/release/manager/infrastructure/unix.py similarity index 100% rename from release/manager/infrastructure/unix.py rename to developer/authored/deprecated/release/manager/infrastructure/unix.py diff --git a/release/manager/text.py b/developer/authored/deprecated/release/manager/text.py similarity index 100% rename from release/manager/text.py rename to developer/authored/deprecated/release/manager/text.py diff --git a/release/manager/uncatelogued/parser.py b/developer/authored/deprecated/release/manager/uncatelogued/parser.py similarity index 100% rename from release/manager/uncatelogued/parser.py rename to developer/authored/deprecated/release/manager/uncatelogued/parser.py diff --git a/shared/dictionary_style-directory.js b/developer/authored/deprecated/shared/dictionary_style-directory.js similarity index 100% rename from shared/dictionary_style-directory.js rename to developer/authored/deprecated/shared/dictionary_style-directory.js diff --git a/shared/document/install_Python.org b/developer/authored/deprecated/shared/document/install_Python.org similarity index 100% rename from shared/document/install_Python.org rename to developer/authored/deprecated/shared/document/install_Python.org diff --git a/shared/document/install_generic.org b/developer/authored/deprecated/shared/document/install_generic.org similarity index 100% rename from shared/document/install_generic.org rename to developer/authored/deprecated/shared/document/install_generic.org diff --git a/shared/document/installation_Python.org b/developer/authored/deprecated/shared/document/installation_Python.org similarity index 100% rename from shared/document/installation_Python.org rename to developer/authored/deprecated/shared/document/installation_Python.org diff --git a/shared/document/installation_generic.org b/developer/authored/deprecated/shared/document/installation_generic.org similarity index 100% rename from shared/document/installation_generic.org rename to developer/authored/deprecated/shared/document/installation_generic.org diff --git a/shared/document/isntall_RT_share.org b/developer/authored/deprecated/shared/document/isntall_RT_share.org similarity index 100% rename from shared/document/isntall_RT_share.org rename to developer/authored/deprecated/shared/document/isntall_RT_share.org diff --git a/shared/document/packages.org b/developer/authored/deprecated/shared/document/packages.org similarity index 100% rename from shared/document/packages.org rename to developer/authored/deprecated/shared/document/packages.org diff --git a/shared/document/setup.js b/developer/authored/deprecated/shared/document/setup.js similarity index 100% rename from shared/document/setup.js rename to developer/authored/deprecated/shared/document/setup.js diff --git a/developer/document/.githolder b/developer/authored/deprecated/shared/linked-project/.gitkeep similarity index 100% rename from developer/document/.githolder rename to developer/authored/deprecated/shared/linked-project/.gitkeep diff --git a/shared/linked-project/RT-project-share b/developer/authored/deprecated/shared/linked-project/RT-project-share similarity index 100% rename from shared/linked-project/RT-project-share rename to developer/authored/deprecated/shared/linked-project/RT-project-share diff --git a/shared/linked-project/RT-style-JS_public b/developer/authored/deprecated/shared/linked-project/RT-style-JS_public similarity index 100% rename from shared/linked-project/RT-style-JS_public rename to developer/authored/deprecated/shared/linked-project/RT-style-JS_public diff --git a/shared/tool/RT-formatter b/developer/authored/deprecated/shared/tool/RT-formatter similarity index 100% rename from shared/tool/RT-formatter rename to developer/authored/deprecated/shared/tool/RT-formatter diff --git a/shared/tool/RT-formatter.el b/developer/authored/deprecated/shared/tool/RT-formatter.el similarity index 100% rename from shared/tool/RT-formatter.el rename to developer/authored/deprecated/shared/tool/RT-formatter.el diff --git a/shared/upstream/.gitignore b/developer/authored/deprecated/shared/upstream/.gitignore similarity index 100% rename from shared/upstream/.gitignore rename to developer/authored/deprecated/shared/upstream/.gitignore diff --git a/tester/manager/sm b/developer/authored/deprecated/tester/manager/sm similarity index 100% rename from tester/manager/sm rename to developer/authored/deprecated/tester/manager/sm diff --git a/tester/manager/test_0.sh b/developer/authored/deprecated/tester/manager/test_0.sh similarity index 100% rename from tester/manager/test_0.sh rename to developer/authored/deprecated/tester/manager/test_0.sh diff --git a/tester/manager/test_0_driver.sh b/developer/authored/deprecated/tester/manager/test_0_driver.sh similarity index 100% rename from tester/manager/test_0_driver.sh rename to developer/authored/deprecated/tester/manager/test_0_driver.sh diff --git a/tester/manager/test_0_expected.sh b/developer/authored/deprecated/tester/manager/test_0_expected.sh similarity index 100% rename from tester/manager/test_0_expected.sh rename to developer/authored/deprecated/tester/manager/test_0_expected.sh diff --git a/tester/manager/test_0_out.sh b/developer/authored/deprecated/tester/manager/test_0_out.sh similarity index 100% rename from tester/manager/test_0_out.sh rename to developer/authored/deprecated/tester/manager/test_0_out.sh diff --git a/tester/manager/test_1_driver.sh b/developer/authored/deprecated/tester/manager/test_1_driver.sh similarity index 100% rename from tester/manager/test_1_driver.sh rename to developer/authored/deprecated/tester/manager/test_1_driver.sh diff --git a/tester/test.sh b/developer/authored/deprecated/tester/test.sh similarity index 100% rename from tester/test.sh rename to developer/authored/deprecated/tester/test.sh diff --git a/tester/test_0.sh b/developer/authored/deprecated/tester/test_0.sh similarity index 100% rename from tester/test_0.sh rename to developer/authored/deprecated/tester/test_0.sh diff --git a/tester/test_0_expected.sh b/developer/authored/deprecated/tester/test_0_expected.sh similarity index 100% rename from tester/test_0_expected.sh rename to developer/authored/deprecated/tester/test_0_expected.sh diff --git a/tester/tool/developer b/developer/authored/deprecated/tester/tool/developer similarity index 100% rename from tester/tool/developer rename to developer/authored/deprecated/tester/tool/developer diff --git a/tester/tool/env b/developer/authored/deprecated/tester/tool/env similarity index 100% rename from tester/tool/env rename to developer/authored/deprecated/tester/tool/env diff --git a/tester/tool/publish b/developer/authored/deprecated/tester/tool/publish similarity index 100% rename from tester/tool/publish rename to developer/authored/deprecated/tester/tool/publish diff --git a/tester/tool/tester b/developer/authored/deprecated/tester/tool/tester similarity index 100% rename from tester/tool/tester rename to developer/authored/deprecated/tester/tool/tester diff --git a/tester/tool/tester_lib.py b/developer/authored/deprecated/tester/tool/tester_lib.py similarity index 100% rename from tester/tool/tester_lib.py rename to developer/authored/deprecated/tester/tool/tester_lib.py diff --git a/developer/deprecated/server.lib.c b/developer/deprecated/server.lib.c deleted file mode 100644 index baf5469..0000000 --- a/developer/deprecated/server.lib.c +++ /dev/null @@ -1,104 +0,0 @@ -#ifndef IFACE -#define Server·IMPLEMENTATION -#define IFACE -#endif - -#ifndef Server·IFACE -#define Server·IFACE - - // Necessary interface includes - #include - #include - #include - - // Interface prototypes - int Server·run(); - -#endif // Server·IFACE - -#ifdef Server·IMPLEMENTATION - - // Implementation-specific includes - #include - #include - #include - #include - #include - - // Constants - #define Server·SOCKET_PATH "/var/user_data/Thomas-developer/subu/developer/mockup/subu_server_home/subu_server.sock" - #define Server·LOG_PATH "server.log" - #define Server·BUFFER_SIZE 256 - - int Server·run(){ - int server_fd ,client_fd; - struct sockaddr_un address; - char buffer[Server·BUFFER_SIZE]; - FILE *log_file; - - // Open the log file - log_file = fopen(Server·LOG_PATH ,"a+"); - if( log_file == NULL ){ - perror("Server·run:: error opening log file"); - return EXIT_FAILURE; - } - - // Create the socket - if( (server_fd = socket(AF_UNIX ,SOCK_STREAM ,0)) == -1 ){ - perror("Server·run:: error creating socket"); - fclose(log_file); - return EXIT_FAILURE; - } - - // Configure socket address - memset(&address ,0 ,sizeof(address)); - address.sun_family = AF_UNIX; - strncpy(address.sun_path ,Server·SOCKET_PATH ,sizeof(address.sun_path) - 1); - - // Bind the socket - unlink(Server·SOCKET_PATH); // Remove existing file if present - if( bind(server_fd ,(struct sockaddr *)&address ,sizeof(address)) == -1 ){ - perror("Server·run:: error binding socket"); - fclose(log_file); - close(server_fd); - return EXIT_FAILURE; - } - - // Listen for connections - if( listen(server_fd ,5) == -1 ){ - perror("Server·run:: error listening on socket"); - fclose(log_file); - close(server_fd); - return EXIT_FAILURE; - } - - printf("Server·run:: server running, waiting for connections...\n"); - - // Accept and handle client connections - while( (client_fd = accept(server_fd ,NULL ,NULL)) != -1 ){ - ssize_t bytes_read; - printf("Server·run:: connection made!\n"); - - memset(buffer ,0 ,Server·BUFFER_SIZE); - bytes_read = read(client_fd ,buffer ,Server·BUFFER_SIZE - 1); - if( bytes_read > 0 ){ - printf("Server·run:: connection said: %s\n" ,buffer); - fprintf(log_file ,"Received: %s\n" ,buffer); - fflush(log_file); - } else if( bytes_read == -1 ){ - perror("Server·run:: error reading from client"); - } - - close(client_fd); - } - - // Clean up - perror("Server·run:: error accepting connection"); - fclose(log_file); - close(server_fd); - unlink(Server·SOCKET_PATH); - - return EXIT_FAILURE; - } - -#endif // Server·IMPLEMENTATION diff --git a/developer/document/SubU_window_Thomas_developer.png b/developer/document/SubU_window_Thomas_developer.png new file mode 100644 index 0000000000000000000000000000000000000000..d25b4e6f788024ea63c58a883949107e66176dd1 GIT binary patch literal 60772 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 VoXhy)Q{r2{(_FeRTEpyz{{yTA&-wrW literal 0 HcmV?d00001 diff --git a/developer/document/naming_file-and-directory.html b/developer/document/naming_file-and-directory.html new file mode 100644 index 0000000..c597855 --- /dev/null +++ b/developer/document/naming_file-and-directory.html @@ -0,0 +1,101 @@ + + + + + File and directory naming conventions + + + + + + + + + + + +

    Program file system objects

    + +

    + Harmony files and directories are considered to be program file systems objects. +

    + +

    Files

    +

    + Files that hold a single class, have he same base name as that class. If a file holds multiple classes, but there is a class of primary importance and auxiliary classes, the file carries the name of the primary class. +

    + +

    + If a file represents a module or name space, follow the same naming conventions specified for classes and name spaces in the format_RT·code.html document. +

    + +

    + Other program files follow the same naming conventions specified in the format_RT·code.html document for an identifier of the + contained or related language. +

    + +

    Hyphens and spaces

    + +

    In a C language culture, file names use underscores between words. In a Lisp language culture use snake-kebab_case. Spaces are generally not used in program file names. Though the Harmony skeleton is language agnostic, it was first used with C, hence file names use primarily underscores. +

    + + +

    Program Directories

    +

    + A directory that is a package, follows the convention for name spaces. +

    + +

    + A directory that is used to give the contained files a property is an identifier. +

    + +

    + What is a reasonable primary property for a set of files? Perhaps: +

    +
      +
    • Who uses each file with this property. Home directories are named like this.
    • +
    • The role of the people using the file. The developer and tester directories were named in this manner.
    • +
    • What program are the files for.
    • +
    • The generic category of program said files are for. We prefer the names authored and made. authored files are those written by humans or AI, while made files are products of tools.
    • +
    + +

    Property indicators on files

    +

    + We add a second property to a file using dot extensions to the file's name. We can extend the dot suffix model by using multiple dot suffixes. File name extensions are used to signal to the build tools how the file is to be processed: +

    +
      +
    • .cli.c : Compiler fodder made into a stand-alone executable.
    • +
    • .lib.c : Library code source, compiled as an object file and added to the project archive.
    • +
    • .mod.c : Kernel module sources.
    • +
    + +

    Exercises

    +

    Exercise 1: Program file names

    +

    + Rename the following poorly named files to strictly adhere to the RT naming conventions. Pay close attention to the language culture, the capitalization of acronyms, and the rule for spelling out abbreviations in outer scope identifiers. +

    +
      +
    • A C-culture makefile that builds a target library and command line interface: target-lib-cli.mk
    • +
    • A Lisp-culture source file for an HTML parsing module: html_parser_mod.el
    • +
    • A C language source file that initializes an HTTP server: init-http-srv.c
    • +
    + +
    + + diff --git a/developer/document/single-file_C-module-and-namespace.html b/developer/document/single-file_C-module-and-namespace.html new file mode 100644 index 0000000..0e6cd17 --- /dev/null +++ b/developer/document/single-file_C-module-and-namespace.html @@ -0,0 +1,184 @@ + + + + + + + C modules, namespaces, and the build lifecycle + + + + + + + + + + + +

    Single-file C source

    +

    + The RT C language culture abandons the traditional separation of declarations into .h header files and definitions into .c source files. A developer integrates the interface and the implementation into a single file. +

    +

    + When a person includes the file using an #include directive, the file exposes only its interface. When the build system compiles the file into an object, a preprocessor macro acts as an implementation gate to compile the definitions. This keeps the source tree clean and ensures the interface and implementation never fall out of synchronization. +

    + +

    Ad hoc namespaces

    +

    + The C language lacks native namespaces. To prevent symbol collisions in large projects, RT code format uses a center dot (·) to denote ad hoc namespaces within identifiers. +

    +

    + A programmer maps the directory structure directly to these namespaces. For example, a file located at authored/ExampleGreet/Math.lib.c belongs to the ExampleGreet namespace and defines the Math module. +

    +
      +
    • Types and modules use PascalCase.
    • +
    • Functions and variables use snake_case.
    • +
    +

    + An exported function from this module carries the full namespace and module prefix, such as ExampleGreet·Math·add. +

    + +

    The implementation gate

    +

    + To achieve the single-file source pattern, the code relies on two preprocessor constructs: +

    +
      +
    • Include Guard: The entire file is wrapped in an include guard using the ·ONCE suffix. This prevents redeclaration errors if the file is included multiple times.
    • +
    • Implementation Block: The definitions are wrapped in a single #ifdef block using the exact namespace and module name. The build system dynamically injects this macro via a -D compiler flag when building the module's specific object file.
    • +
    + +

    Build system mechanics

    +

    + When a consumer file, such as hello.CLI.c, contains #include "Math.lib.c", the compiler processes the file without the ExampleGreet·Math macro defined. It skips the implementation and reads only the function prototype. +

    +

    + When the orchestrator compiles the library object, it evaluates the target name and explicitly passes -DExampleGreet·Math to the compiler. This unlocks the gate, compiling the machine code for the definitions into Math.lib.o. +

    + +

    Example: Math.lib.c

    +

    + The following example demonstrates the complete structure. +

    + + + #ifndef ExampleGreet·Math·ONCE + #define ExampleGreet·Math·ONCE + + int ExampleGreet·Math·add(int a ,int b); + + #ifdef ExampleGreet·Math + + int ExampleGreet·Math·add(int a ,int b){ + return a + b; + } + + #endif // ExampleGreet·Math + #endif // ExampleGreet·Math·ONCE + + +

    Cross-module dependencies

    +

    + When one module depends on another, the developer directly includes the library source file. For example, if the Greeter module requires the Math module, the file Greeter.lib.c will contain: +

    + + #include "Math.lib.c" + +

    + Because every file is protected by a ·ONCE include guard, it is safe for multiple modules to include the same dependency. The preprocessor will expand the interface once per translation unit. +

    + +

    Information hiding

    +

    + To define internal helper functions or private data that should not be exposed in the module's public interface, a programmer places them strictly inside the #ifdef implementation block. +

    +

    + To prevent these internal symbols from leaking into the global namespace during linking, they must be given internal linkage. The RT skeleton provides the Local macro (defined as static in RT_global.h) for this exact purpose. +

    + + #ifdef ExampleGreet·Math + + Local int internal_helper(int val){ + return val * 2; + } + + int ExampleGreet·Math·add(int a ,int b){ + return internal_helper(a) + b; + } + + #endif // ExampleGreet·Math + + +

    Executable entry points

    +

    + Programs intended to be compiled into standalone executables use the .CLI.c suffix. These files consume the library modules but do not define their own namespaces or include guards, as they are never included by other files. +

    +

    + A .CLI.c file includes the necessary .lib.c files, parses command-line arguments in the main function, and passes native data types to a dedicated CLI() function to orchestrate the core logic. +

    + +

    Directory structure and namespaces

    +

    + The physical layout of the authored directory dictates how the build orchestrator finds the source code. +

    +
      +
    • Flat Structure: If a project does not utilize namespaces, the .lib.c and .CLI.c files reside directly inside the developer/authored/ directory.
    • +
    • Namespaced Structure: If a project utilizes namespaces, the files are grouped into subdirectories matching the namespace name, such as developer/authored/ExampleGreet/.
    • +
    + +

    Making the code

    +

    + The developer compiles the code using the make wrapper script located in developer/tool/make. This script accepts two primary arguments: the build command and the target namespace. +

    + + + > make <command> [namespace] + + +

    + To build the ExampleGreet code, a person issues the following command from the developer workspace: +

    + + + > make all ExampleGreet + + +

    + Behind the scenes, the make wrapper assigns the namespace to an environment variable and calls the master orchestrator makefile. The orchestrator detects the namespace, updates the source directory search path to authored/ExampleGreet, and injects the appropriate namespace macros during compilation. +

    +

    + The orchestrator compiles the .lib.c files into object files under scratchpad/build/object/, archives them into a library file, and links the .CLI.c objects into standalone executables. The final artifacts are placed in the scratchpad/made/ directory. +

    + +

    Promoting the artifacts

    +

    + Artifacts resting in the scratchpad are volatile and private to the developer. To share the executables and libraries with the rest of the project (such as the tester or consumer roles), the developer must promote them. +

    + + + > promote write + + +

    + The promote tool compares the contents of developer/scratchpad/made/ against consumer/made/. It atomically copies any new or updated files to the consumer workspace, stripping write permissions to enforce the immutability of the deployment target. Once promoted, the hello executable is ready for testing. +

    + +
    + + diff --git a/document/.githolder b/developer/experiment/.gitkeep similarity index 100% rename from document/.githolder rename to developer/experiment/.gitkeep diff --git a/developer/scratchpad/.gitignore b/developer/scratchpad/.gitignore new file mode 100644 index 0000000..120f485 --- /dev/null +++ b/developer/scratchpad/.gitignore @@ -0,0 +1,2 @@ +* +!/.gitignore diff --git a/developer/tool/build b/developer/tool/build new file mode 100755 index 0000000..547fc4c --- /dev/null +++ b/developer/tool/build @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*- +""" +developer/tool/build - Build Orchestrator Wrapper + +This script acts as the primary entry point for staging the project. +It parses colon-separated arguments mapping specific namespaces to their +intended build arguments. + +It enforces the structural convention where authored directories must +be named .. The script delegates execution to +developer/tool/build_component/. +""" + +import sys, os, subprocess, fnmatch +from typing import Dict, Tuple + +def check_environment() -> None: + setup_must_be = "developer/tool/setup" + current_setup = os.environ.get("SETUP", "") + if current_setup != setup_must_be: + print(f"developer/tool/build:: error: must be run in the {setup_must_be} environment", file=sys.stderr) + sys.exit(1) + +def get_namespaces() -> Dict[str, Tuple[str, str]]: + authored_dir = os.path.join(os.environ.get("REPO_HOME", "."), "developer", "authored") + if not os.path.isdir(authored_dir): + return {} + + namespaces = {} + for TM_item in os.listdir(authored_dir): + path = os.path.join(authored_dir, TM_item) + if os.path.isdir(path) and not TM_item.startswith("."): + parts = TM_item.rsplit(".", 1) + if len(parts) == 2: + ns = parts[0] + tool = parts[1] + namespaces[ns] = (tool, TM_item) + else: + print(f"(warning) Ignoring '{TM_item}': directory lacks the required . suffix.", file=sys.stderr) + + return dict(sorted(namespaces.items())) + +def print_usage(namespaces: Dict[str, Tuple[str, str]]) -> None: + print("Usage: build [[:]*]*\n") + print("Arguments are passed directly to the component specified by the directory suffix.\n") + + print("Available namespaces:") + if namespaces: + for TM_ns, (TM_tool, TM_raw_dir) in namespaces.items(): + print(f" {TM_ns:<20} (Component: {TM_tool}, Dir: {TM_raw_dir})") + else: + print(" (No conforming namespaces currently found in authored/)") + + print("\nExamples:") + print(" build (Displays this usage message)") + print(" build ExampleGreet (Executes 'all' via its assigned component)") + print(" build Ex*:library RT:all:information (Batch execution with pattern matching)") + print(" build \"*:clean\" (Cleans all namespaces. Quotes prevent Bash expansion)") + +def run_build(argument: str, namespace: str, tool: str, raw_dir: str) -> None: + tool_path = os.path.join("tool", "build_component", tool) + + if not os.path.isfile(tool_path) or not os.access(tool_path, os.X_OK): + print(f"error: Build component '{tool}' for namespace '{namespace}' is missing or not executable at {tool_path}", file=sys.stderr) + sys.exit(1) + + env = os.environ.copy() + env["NAMESPACE"] = namespace + env["NAMESPACE_DIR"] = raw_dir + + tool_cmd = [tool_path, argument] + + try: + subprocess.run(tool_cmd, env=env, check=True) + except subprocess.CalledProcessError as e: + print(f"Build component '{tool}' failed for namespace '{namespace}' with exit code {e.returncode}", file=sys.stderr) + sys.exit(e.returncode) + +def CLI() -> None: + check_environment() + + repo_home = os.environ.get("REPO_HOME", ".") + dev_dir = os.path.join(repo_home, "developer") + os.chdir(dev_dir) + + args = sys.argv[1:] + namespaces = get_namespaces() + + if not args or args[0] in ["usage", "help", "-h", "--help"]: + print_usage(namespaces) + sys.exit(0) + + matched_any = False + ns_keys = list(namespaces.keys()) + + for TM_arg in args: + parts = TM_arg.split(":") + pattern = parts[0] + arguments = parts[1:] + + if not arguments: + arguments = ["all"] + + matches = fnmatch.filter(ns_keys, pattern) + + if not matches: + print(f"(warning) Pattern '{pattern}' matched no namespaces.", file=sys.stderr) + continue + + matched_any = True + for TM_ns in matches: + tool, raw_dir = namespaces[TM_ns] + for TM_argument in arguments: + run_build(TM_argument, TM_ns, tool, raw_dir) + + if not matched_any: + sys.exit(1) + + print("build done.") + +if __name__ == "__main__": + CLI() diff --git a/developer/tool/build_component/copy b/developer/tool/build_component/copy new file mode 100755 index 0000000..0a1998c --- /dev/null +++ b/developer/tool/build_component/copy @@ -0,0 +1,59 @@ +#!/usr/bin/env -S python3 -B +# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*- +# developer/tool/build_component/copy + +import sys +import os +import shutil + +sys.path.insert(0, os.path.join(os.path.abspath("tool"), "build_component")) +import sync_engine + +HELP = """usage: copy {all|clean|help} + all Stages files from authored to scratchpad/made/. + clean Removes scratchpad/made/. + help Shows this message. +""" + +def cmd_all(): + namespace = os.environ.get("NAMESPACE") + namespace_dir = os.environ.get("NAMESPACE_DIR") + + if not namespace or not namespace_dir: + print("copy: error: NAMESPACE and NAMESPACE_DIR environment variables must be set.", file=sys.stderr) + sys.exit(1) + + src_root = os.path.join("authored", namespace_dir) + dst_root = os.path.join("scratchpad", "made", namespace) + + sync_engine.execute_sync(src_root, dst_root, prefix="copy", apply_modes=False) + +def cmd_clean(): + namespace = os.environ.get("NAMESPACE") + if not namespace: + print("copy: error: NAMESPACE environment variable must be set.", file=sys.stderr) + sys.exit(1) + + dst_root = os.path.join("scratchpad", "made", namespace) + if os.path.exists(dst_root): + shutil.rmtree(dst_root) + print(f"copy: Cleaned {dst_root}") + +def CLI(): + if len(sys.argv) < 2: + cmd = "all" + else: + cmd = sys.argv[1] + + if cmd == "all": + cmd_all() + elif cmd == "clean": + cmd_clean() + elif cmd in ["help", "-h", "--help"]: + print(HELP) + else: + print(f"copy: Unrecognized command '{cmd}'", file=sys.stderr) + sys.exit(1) + +if __name__ == "__main__": + CLI() diff --git a/developer/tool/build_component/make b/developer/tool/build_component/make new file mode 100755 index 0000000..30fb82d --- /dev/null +++ b/developer/tool/build_component/make @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +# developer/tool/build_component/make + +set -euo pipefail +CMD="${1:-all}" + +# The GNU Make orchestrator expects C_SOURCE_DIR to point to the raw directory +export C_SOURCE_DIR="authored/$NAMESPACE_DIR" +export KMOD_SOURCE_DIR="authored/$NAMESPACE_DIR" + +# Isolate build products into their specific namespace directory +export LIBRARY_DIR="scratchpad/made/$NAMESPACE" +export MACHINE_DIR="scratchpad/made/$NAMESPACE" + +/bin/make -f tool/build_component/makefile "$CMD" diff --git a/developer/tool/build_component/makefile b/developer/tool/build_component/makefile new file mode 100644 index 0000000..7914c0b --- /dev/null +++ b/developer/tool/build_component/makefile @@ -0,0 +1,55 @@ +# developer/tool/build_component/makefile +.SUFFIXES: +.EXPORT_ALL_VARIABLES: + +RT_MAKEFILE_DP := $(REPO_HOME)/shared/tool/makefile + +.PHONY: usage +usage: + @printf "Usage: make [usage|version|information|all|lib|CLI|kmod|clean]\n" + +.PHONY: version +version: + @printf "local ----------------------------------------\n" + @echo tool/makefile version 2.0 + @printf "Harmony.mk ----------------------------------------\n" + @$(MAKE) -f $(RT_MAKEFILE_DP)/Harmony.mk version + @printf "target_kernel-module.mk ----------------------------------------\n" + @$(MAKE) -f $(RT_MAKEFILE_DP)/target_kernel-module.mk version + +.PHONY: information +information: + @printf "local ----------------------------------------\n" + -@echo CURDIR='$(CURDIR)' + @echo REPO_HOME="$(REPO_HOME)" + @echo NAMESPACE="$(NAMESPACE)" + @echo C_SOURCE_DIR="$(C_SOURCE_DIR)" + @echo KMOD_BUILD_DIR="/lib/modules/$(shell uname -r)/build" + @echo CURDIR="$(CURDIR)" + @printf "Harmony.mk ----------------------------------------\n" + @$(MAKE) -f $(RT_MAKEFILE_DP)/Harmony.mk information + @printf "target_kernel-module.mk ----------------------------------------\n" + @$(MAKE) -f $(RT_MAKEFILE_DP)/target_kernel-module.mk information + +.PHONY: all +all: library CLI kmod + +.PHONY: library lib +library lib: + @$(MAKE) -f $(RT_MAKEFILE_DP)/Harmony.mk library + +.PHONY: CLI +CLI: + @$(MAKE) -f $(RT_MAKEFILE_DP)/Harmony.mk CLI + +.PHONY: kmod +kmod: + @$(MAKE) -f $(RT_MAKEFILE_DP)/target_kernel-module.mk kmod + +.PHONY: clean +clean: + @printf "local ----------------------------------------\n" + @printf "Harmony.mk ----------------------------------------\n" + @$(MAKE) -f $(RT_MAKEFILE_DP)/Harmony.mk clean + @printf "target_kernel-module.mk ----------------------------------------\n" + @$(MAKE) -f $(RT_MAKEFILE_DP)/target_kernel-module.mk clean diff --git a/developer/tool/build_component/sync_engine.py b/developer/tool/build_component/sync_engine.py new file mode 100644 index 0000000..cf6412b --- /dev/null +++ b/developer/tool/build_component/sync_engine.py @@ -0,0 +1,80 @@ +#!/usr/bin/env -S python3 -B +# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*- +# developer/tool/sync_engine.py + +import os +import sys +import shutil +import filecmp +import stat +import tempfile + +def copy_file_atomic(src_abs, dst_abs, mode, dry=False, prefix="sync"): + if dry: + print(f"(dry) {prefix}: install -m {oct(mode)[2:]} '{src_abs}' -> '{dst_abs}'") + return + + parent = os.path.dirname(dst_abs) + os.makedirs(parent, exist_ok=True) + + fd, tmp_path = tempfile.mkstemp(prefix=".tmp.", dir=parent) + try: + with os.fdopen(fd, "wb") as tmpf, open(src_abs, "rb") as sf: + shutil.copyfileobj(sf, tmpf) + tmpf.flush() + os.chmod(tmp_path, mode) + os.replace(tmp_path, dst_abs) + except Exception as e: + if os.path.exists(tmp_path): + os.unlink(tmp_path) + raise e + +def execute_sync(src_root, dst_root, prefix="sync", dry=False, apply_modes=False, ignore_list=None): + wrote = False + if ignore_list is None: + ignore_list = [] + + if not os.path.isdir(src_root): + print(f"{prefix}: error: Source directory '{src_root}' does not exist.", file=sys.stderr) + return False + + for root, dirs, files in os.walk(src_root): + dirs.sort() + files.sort() + for fn in files: + if fn in ignore_list: + continue + + src_abs = os.path.join(root, fn) + rel = os.path.relpath(src_abs, src_root) + dst_abs = os.path.join(dst_root, rel) + + st = os.stat(src_abs) + if apply_modes: + is_exec = st.st_mode & stat.S_IXUSR + mode = 0o500 if is_exec else 0o400 + else: + mode = stat.S_IMODE(st.st_mode) + + if not os.path.exists(dst_abs): + copy_file_atomic(src_abs, dst_abs, mode, dry, prefix) + if not dry: print(f"{prefix}: Added {rel}") + wrote = True + continue + + if filecmp.cmp(src_abs, dst_abs, shallow=False): + continue + + dst_st = os.stat(dst_abs) + if dst_st.st_mtime > st.st_mtime: + print(f"{prefix}: error: {rel} skipped. Destination version is newer.") + continue + + copy_file_atomic(src_abs, dst_abs, mode, dry, prefix) + if not dry: print(f"{prefix}: Updated {rel}") + wrote = True + + if not wrote and not dry: + print(f"{prefix}: (info) nothing new to synchronize from {src_root}") + + return wrote diff --git a/developer/tool/promote b/developer/tool/promote new file mode 100755 index 0000000..7292374 --- /dev/null +++ b/developer/tool/promote @@ -0,0 +1,261 @@ +#!/usr/bin/env -S python3 -B +# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*- +# developer/tool/promote + +import os +import sys +import shutil +import stat +import pwd +import grp +import filecmp + +# Calculate the path relative to this script's location +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +# Assumes this script is in developer/tool/ +# sync_engine is in developer/tool/build_component +sys.path.insert(0, os.path.join(SCRIPT_DIR, "build_component")) +import sync_engine + +HELP = """usage: promote {write|clean|ls|diff|help|dry write} + write Copies what is staged in scratchpad/made into consumer. Only updates newer files. + clean Remove all contents of the consumer directory (preserves .gitignore). + ls List consumer as an indented tree: PERMS OWNER NAME. + diff List missing, orphaned, or out of sync files between scratchpad/made and consumer. + help Show this message. + dry write Preview what write would do without modifying the filesystem. + +Promoted files are read-only: 400, or 500 when executable. Directories below +consumer/ are 500, so not even the owner can add, replace or remove a file in +them; write and clean open them (700) for the duration and lock them again. +consumer/ itself stays 700, as it holds the .gitignore git manages. +""" + +SETUP_MUST_BE = "developer/tool/setup" +DEFAULT_DIR_MODE = 0o700 +LOCKED_DIR_MODE = 0o500 + +def exit_with_status(msg, code=1): + print(f"promote: {msg}", file=sys.stderr) + sys.exit(code) + +def assert_setup(): + setup_val = os.environ.get("SETUP", "") + if setup_val != SETUP_MUST_BE: + hint = ( + "SETUP is not 'developer/tool/setup'.\n" + "Enter the developer role with: ./setup developer\n" + "That script exports: ROLE=developer; SETUP=$ROLE/tool/setup" + ) + exit_with_status(f"bad environment: SETUP='{setup_val}'. {hint}") + +def repo_home(): + rh = os.environ.get("REPO_HOME") + if not rh: + exit_with_status("REPO_HOME not set") + return rh + +def dpath(*parts): + return os.path.join(repo_home(), "developer", *parts) + +def cpath(*parts): + return os.path.join(repo_home(), "consumer", *parts) + +def ensure_mode(path_str, mode): + try: os.chmod(path_str, mode) + except Exception: pass + +def ensure_dir(path_str, mode=DEFAULT_DIR_MODE, dry=False): + if dry: + return + os.makedirs(path_str, exist_ok=True) + ensure_mode(path_str, mode) + +def consumer_subdirs(root_dp): + """Every directory below root_dp; root_dp itself is not included.""" + found = [] + for TM_root, TM_dirs, TM_files in os.walk(root_dp): + for TM_d in TM_dirs: + found.append(os.path.join(TM_root, TM_d)) + return found + +def set_subdir_modes(root_dp, mode): + for TM_d in consumer_subdirs(root_dp): + ensure_mode(TM_d, mode) + +def filemode(m): + try: return stat.filemode(m) + except Exception: return oct(m & 0o777) + +def owner_group(st): + try: return f"{pwd.getpwuid(st.st_uid).pw_name}:{grp.getgrgid(st.st_gid).gr_name}" + except Exception: return f"{st.st_uid}:{st.st_gid}" + +def list_tree(root_dp): + if not os.path.isdir(root_dp): + return + + entries = [] + def gather(path_str, depth, is_root): + try: + it = list(os.scandir(path_str)) + except FileNotFoundError: + return + dirs = [TM_e for TM_e in it if TM_e.is_dir(follow_symlinks=False)] + files = [TM_e for TM_e in it if not TM_e.is_dir(follow_symlinks=False)] + dirs.sort(key=lambda TM_e: TM_e.name) + files.sort(key=lambda TM_e: TM_e.name) + + if is_root: + for TM_f in (TM_e for TM_e in files if TM_e.name.startswith(".")): + st = os.lstat(TM_f.path) + entries.append((False, depth, filemode(st.st_mode), owner_group(st), TM_f.name)) + for TM_d in dirs: + st = os.lstat(TM_d.path) + entries.append((True, depth, filemode(st.st_mode), owner_group(st), TM_d.name + "/")) + gather(TM_d.path, depth + 1, False) + for TM_f in (TM_e for TM_e in files if not TM_e.name.startswith(".")): + st = os.lstat(TM_f.path) + entries.append((False, depth, filemode(st.st_mode), owner_group(st), TM_f.name)) + else: + for TM_d in dirs: + st = os.lstat(TM_d.path) + entries.append((True, depth, filemode(st.st_mode), owner_group(st), TM_d.name + "/")) + gather(TM_d.path, depth + 1, False) + for TM_f in files: + st = os.lstat(TM_f.path) + entries.append((False, depth, filemode(st.st_mode), owner_group(st), TM_f.name)) + + gather(root_dp, 1, True) + + ogw = 0 + for TM_isdir, TM_depth, TM_perms, TM_ownergrp, TM_name in entries: + if len(TM_ownergrp) > ogw: + ogw = len(TM_ownergrp) + + print("consumer/") + for TM_isdir, TM_depth, TM_perms, TM_ownergrp, TM_name in entries: + indent = " " * TM_depth + print(f"{TM_perms} {TM_ownergrp:<{ogw}} {indent}{TM_name}") + +def cmd_write(dry=False): + dst_root = cpath() + ensure_dir(dst_root, DEFAULT_DIR_MODE, dry=dry) + src_root = dpath("scratchpad", "made") + + # Files are replaced by writing a temporary file beside them and renaming + # it into place, which needs write permission on the directory. + if not dry: set_subdir_modes(dst_root, DEFAULT_DIR_MODE) + try: + sync_engine.execute_sync( + src_root, + dst_root, + prefix="promote", + dry=dry, + apply_modes=True, + ignore_list=[".gitignore"] + ) + finally: + if not dry: set_subdir_modes(dst_root, LOCKED_DIR_MODE) + +def cmd_diff(): + dst_root = cpath() + src_root = dpath("scratchpad", "made") + + src_files = set() + if os.path.isdir(src_root): + for TM_root, TM_dirs, TM_files in os.walk(src_root): + for TM_fn in TM_files: + if TM_fn == ".gitignore": + continue + src_files.add(os.path.relpath(os.path.join(TM_root, TM_fn), src_root)) + + dst_files = set() + if os.path.isdir(dst_root): + for TM_root, TM_dirs, TM_files in os.walk(dst_root): + for TM_fn in TM_files: + if TM_fn == ".gitignore": + continue + dst_files.add(os.path.relpath(os.path.join(TM_root, TM_fn), dst_root)) + + if not src_files and not dst_files: + print("No differences found. Both directories are empty or missing.") + return + + all_files = sorted(list(src_files | dst_files)) + found_diff = False + + for TM_rel in all_files: + if TM_rel not in dst_files: + print(f"Pending promotion (missing in consumer): {TM_rel}") + found_diff = True + elif TM_rel not in src_files: + print(f"Orphaned in consumer (missing in scratchpad/made): {TM_rel}") + found_diff = True + else: + src_abs = os.path.join(src_root, TM_rel) + dst_abs = os.path.join(dst_root, TM_rel) + + if not filecmp.cmp(src_abs, dst_abs, shallow=False): + src_mtime = os.stat(src_abs).st_mtime + dst_mtime = os.stat(dst_abs).st_mtime + + if src_mtime > dst_mtime: + print(f"Pending update (contents differ, newer in scratchpad/made): {TM_rel}") + else: + print(f"Contents differ (locally modified in consumer): {TM_rel}") + found_diff = True + + if not found_diff: + print("No differences found. Consumer matches developer scratchpad/made.") + +def cmd_clean(): + consumer_root_dir = cpath() + if not os.path.isdir(consumer_root_dir): + return + # Removing an entry needs write permission on the directory holding it. + set_subdir_modes(consumer_root_dir, DEFAULT_DIR_MODE) + for TM_name in os.listdir(consumer_root_dir): + if TM_name == ".gitignore": + continue + p = os.path.join(consumer_root_dir, TM_name) + if os.path.isdir(p) and not os.path.islink(p): + shutil.rmtree(p, ignore_errors=True) + else: + try: os.unlink(p) + except FileNotFoundError: pass + +def CLI(): + assert_setup() + + repo_home = os.environ.get("REPO_HOME", ".") + dev_dir = os.path.join(repo_home, "developer") + os.chdir(dev_dir) + + if len(sys.argv) < 2: + print(HELP) + return + + cmd = sys.argv[1] + args = sys.argv[2:] + + if cmd == "write": + cmd_write(dry=False) + elif cmd == "clean": + cmd_clean() + elif cmd == "ls": + list_tree(cpath()) + elif cmd == "diff": + cmd_diff() + elif cmd == "help": + print(HELP) + elif cmd == "dry": + if args and args[0] == "write": + cmd_write(dry=True) + else: + print(HELP) + else: + print(HELP) + +if __name__ == "__main__": + CLI() diff --git a/developer/tool/setup b/developer/tool/setup new file mode 100644 index 0000000..0b993ad --- /dev/null +++ b/developer/tool/setup @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +script_afp=$(realpath "${BASH_SOURCE[0]}") + diff --git a/document/introduction_Harmony.html b/document/introduction_Harmony.html new file mode 100644 index 0000000..432c63b --- /dev/null +++ b/document/introduction_Harmony.html @@ -0,0 +1,249 @@ + + + + + Introduction to Harmony + + + + + + + + + + + +

    Purpose

    +

    + Harmony is a project skeleton: a language-agnostic directory structure and a set of maintenance tools, kept in a git repository, from which new projects are made. It is designed for long-lived software developed by a team, and serves equally well for one person, who takes on each role in turn. The structure exists to give: +

    +
      +
    1. Clarity about where things live. +
        +
      • Role-based work areas.
      • +
      • Separation of skeleton, team member authored, machine-made, and depended-upon software.
      • +
      +
    2. +
    3. A safe, predictable build and promotion workflow.
    4. +
    + +

    Making a project

    +

    + A project administrator makes a new project from a clone of Harmony, by taking on the administrator role and running new-project with the name of the new project: +

    + + > cd Harmony + > ./setup administrator + > new-project <name> + +

    + new-project makes the new project as a sibling directory of Harmony, then: +

    +
      +
    1. Copies the files of Harmony's last commit, leaving out Harmony's git history, untracked files, and uncommitted changes.
    2. +
    3. Renames 0pus_Harmony to 0pus_<name>.
    4. +
    5. Adds a line for the new project to shared/tool/version.
    6. +
    7. Initializes a git repository on the core-developer_branch branch, and commits the copied skeleton.
    8. +
    9. Adds any remote repositories given as -remote <url>.
    10. +
    +

    + Because the new project starts with its own git history, work done in it cannot be checked in to the Harmony skeleton by accident. +

    +

    + Leave the Harmony line in the version file. It records which skeleton the project was made from, so that an administrator can later see what has changed in the skeleton since, and bring the project up to date. The administrator tool skeleton_diff compares a project with a Harmony checkout for this purpose. +

    + +

    Taking on a role

    +

    + A person works in a project by taking on a role. The roles are administrator, developer, tester, and consumer. From the top directory of the project, run setup with the role: +

    + + > ./setup developer + +

    + setup starts a new bash shell configured for the role, and leaves the person in the role's directory. The prompt shows the project and the role. exit leaves the role and returns to the shell setup was run from. It is common to have several terminal windows or Emacs shells open, each in a different role. +

    +

    + Python programmers will recognize this as similar to a virtual environment. Harmony calls it the project setup, and the command is setup rather than activate. Unlike a Python virtual environment, there is one setup for each role. +

    +

    + In the new shell, the setup: +

    +
      +
    • Sets ROLE, ROLE_HOME and SETUP, and puts <role>/tool on the PATH.
    • +
    • Sources the project-wide shared/tool/setup, which sets REPO_HOME and PROJECT, puts shared/tool and shared/authored on the PATH, and sources each project dependency's .init file.
    • +
    • Sources shared/authored/setup, when the project has one, for project-specific additions to every role.
    • +
    • Changes into the role's directory and sources <role>/tool/setup, the setup customized for that role in this project.
    • +
    • When running inside Emacs, copies the environment to Emacs with env_to_emacs.
    • +
    +

    + Tools rely on this environment. Absolute paths are formed from REPO_HOME, and some tools, such as build and promote, refuse to run outside the role they belong to. +

    + +

    Roles and their directories

    +

    + Each role has a directory at the top of the project. The shared directory holds what all roles use. +

    + +

    Administrator

    +

    + The administrator keeps the project in step with the Harmony skeleton, maintains the role environments, installs project dependencies, and makes releases. administrator/tool holds the tools for this, among them new-project, skeleton_diff, and archive, which writes an archive of the repository to the scratchpad. +

    + +

    Developer

    +

    + The developer writes the project's source and builds it. +

    +
      +
    • authored/ : Source written by the team. Tracked.
    • +
    • document/ : Documents for developers, such as coding and naming conventions.
    • +
    • experiment/ : Informal, short-lived trials.
    • +
    • scratchpad/ : Untracked. Intermediate build files and the staged products of a build.
    • +
    • tool/ : The developer's tools, including build and promote.
    • +
    + +

    Tester

    +

    + The tester holds the formal tests, including regression suites, and runs them against the work product in consumer/. A developer may keep informal tests in developer/experiment. A test that is to be kept is moved to tester/, which keeps writing code and validating it apart. +

    + +

    Consumer

    +

    + consumer/ receives the work product when a developer promotes it. It is untracked, apart from its .gitignore, and is treated as a deployment target: files arrive there only through promote, and nothing is built in it. The consumer role uses the work product as its end users would. +

    + +

    Shared

    +
      +
    • shared/tool/ : Tools for all roles, and the project-wide setup. The version command prints the version lines.
    • +
    • shared/authored/ : Project-specific additions for all roles. It is on every role's PATH, and may hold a setup that every role sources.
    • +
    • shared/project-dependency/ : Software the project uses but did not author. See "Project dependencies" below.
    • +
    + +

    Building and promoting

    +

    + Building and promotion are separate steps. In the developer role, build makes the work product from developer/authored and stages it on the developer's scratchpad. promote write then copies what changed to consumer/. The result is a promotion candidate, which stays as it is until the next promotion. +

    +

    + Source in developer/authored is grouped into directories named <Namespace>.<Tool>. The suffix names the build component in developer/tool/build_component that builds that directory, such as make for C, or copy for files used as they are. Given no arguments, build prints its usage and the namespaces it found. promote given no arguments prints its commands, among them dry write, which shows what write would copy. +

    +

    + The tester runs tests against the promotion candidate. Tests read from consumer/ and never write to it, though a tester may copy files elsewhere to experiment with them. A developer often has a second window open in the tester role, and cycles through edit, build, promote and test. +

    + +

    After cloning a project

    +

    + Built products are not tracked, so a fresh clone has an empty consumer/. A team member who needs the work product builds it, taking on the developer role to do so: +

    + + > ./setup developer + > build <namespace> + > promote write + > exit + > ./setup <role> + +

    + The developer documents any project-specific build steps in developer/document/build.html. A fresh clone also lacks the project's dependencies, which each team member installs as described beside each dependency in shared/project-dependency. +

    + +

    Project dependencies

    +

    + Harmony is language agnostic. When a project uses C, Python, NodeJS, Java, or other software it did not author, the administrator installs that software as a sibling directory of the project and links it in under shared/project-dependency, with an .init file that every role's setup sources to put the dependency into the environment. Installation notes for each dependency sit beside its .init file, so that each team member can repeat the installation after cloning. +

    +

    + The directory comes with a project link to the directory holding the project and its siblings, through which dependencies are linked, and with Python as an example dependency. The steps are given in administrator/document/how-to_add-project-dependency.html. +

    + +

    Directory names

    +

    + A directory name is a property of each file in the directory, so a full path reads as a description of the file. Because a name states a property of each file, it is rarely plural: when every file in a directory is a test, the directory is named test. +

    +

    + A conventional file system can only approximate a property-based one. Each file sits in one directory, so it receives one property per level of the path. Files sharing several properties can be given a compound name with the properties separated by an underscore, but overlapping groupings cannot be expressed. +

    +

    + When choosing a directory name, prefer properties in this order: +

    +
      +
    • Role (administrator, developer, tester, consumer): the role, human or AI, that works with the files.
    • +
    • Provenance (authored, made): whether a person wrote the file or a tool produced it.
    • +
    • Capability (tool, document, experiment): what the file is for.
    • +
    • Lifecycle (scratchpad, stage): how long the file is expected to last, or where it is in promotion.
    • +
    • Tracking (tracked, untracked): what version control does with it.
    • +
    + +

    Authored, made, scratchpad, inherited

    +

    + Files in a directory named authored were written by the project's team. They did not come with the skeleton, nor with the installation of other software, and they typically constitute the project's intellectual property. Build tools treat authored directories as read-only. All source that is built into a promotion or a release is placed in developer/authored. +

    +

    + Files that came with the skeleton are inherited. When the team edits one, such as developer/tool/build, it becomes a customization. New documents go in document directories and new tools in tool directories. Because the Harmony line stays in the version file, an administrator can tell which skeleton files a project has customized and which it has added. +

    +

    + Files in a directory named scratchpad are untracked, so a clone always has empty scratchpads. Tools place intermediate and staged files there, each in its own subdirectory, so a person who knows those names can also use a scratchpad as a temporary directory. The scratchpad tool maintains the scratchpad of the current directory. Note that its clear command deletes everything on that scratchpad. +

    + +

    Documents

    +

    + Each directory named document holds documents for the role, or the scope, of its parent: +

    +
      +
    • document/ : The project as a whole, such as this introduction.
    • +
    • administrator/document/ : Maintaining the project skeleton, adding dependencies, and making releases.
    • +
    • developer/document/ : Coding standards, conventions, and the build.
    • +
    • tester/document/ : Test plans and testing tools.
    • +
    • consumer/<namespace>/document/ : Documents released with the work product, such as manuals and API references.
    • +
    +

    + Documents are written in HTML with the RT semantic tags, and formatted by RT-Style, a separate project at https://github.com/Thomas-Walker-Lynch/RT-Style. A document loads RT-Style through a short script block at the top of its head. To write a new document, copy an existing one, keep that block as it is, and type over the contents. +

    +

    + Documents are read in Chrome. With nothing set up, a document loads RT-Style from style.ReasoningTechnology.com. To read without a network connection, or against a local copy of RT-Style, open that copy's Manuscript/setup.html once in Chrome. Chrome then loads RT-Style from it for every local document. +

    +

    + document/writing_tool-script.html gives the conventions for writing tools. +

    + +

    What git tracks

    +

    + Everything is tracked except: +

    +
      +
    • consumer/, apart from its .gitignore.
    • +
    • Every scratchpad/, apart from its .gitignore.
    • +
    • In shared/project-dependency/, whatever its .gitignore does not list as tracked.
    • +
    • Archives, editor backups, and compiled files, as listed in the top-level .gitignore.
    • +
    + +

    Workflow and releases

    +

    + The project moves forward through a cycle: the developer promotes a candidate, the tester tests it and files issues, and the developer fixes them and promotes again. The core-developer_branch is the branch the core development team works on. When the product manager judges the work product reliable and complete enough, the administrator makes a project release on a branch named release_v<major>, and tags it. +

    +

    + These documents give the details: +

    +
      +
    • document/role-and-workflow_product-development.html : The roles, and how their work interacts.
    • +
    • document/role-and-workflow_product-maintenance.html : Issues, triage, and patching releases.
    • +
    • administrator/document/how-to_release.html : Making a release.
    • +
    + +
    + + diff --git a/document/role-and-workflow_product-development.html b/document/role-and-workflow_product-development.html new file mode 100644 index 0000000..411b732 --- /dev/null +++ b/document/role-and-workflow_product-development.html @@ -0,0 +1,134 @@ + + + + + Product development roles and workflow + + + + + + + + + + + +

    Roles as hats

    +

    + In Harmony, a role is a hat a person wears. There can be multiple people sharing a single role, and a single person can wear many hats. Some roles interact directly with the project directory structure, while others guide the process from outside the codebase. +

    + +

    Workspace roles

    +

    + These roles interact directly with the repository. To enter a workspace, change directory to the top-level of the project and source the setup file for the desired role: +

    + + > ./setup administrator + > ./setup developer + > ./setup tester + > ./setup consumer + + +

    This will run a new shell with the proper environment setting. It is common for a person to have multiple shells, terminal sessions, or IDEs open, each running under a different role environment. Development and tests is a common pair, though tests are intended to be production tests. The developer has an experiment directory for experiments. +

    + +

    Administrator role

    +

    Responsibilities:

    +
      +
    1. Set up the project directory and keep it in sync with the Harmony skeleton.
    2. +
    3. Maintain role environments (apart from role-specific tool/setup files).
    4. +
    5. Install and maintain shared and third_party tools, addressing issues with the project workflow. Note that the term "third_party" encompasses any software not authored within this specific project.
    6. +
    + +

    Developer role

    +

    Responsibilities and Boundaries:

    +
      +
    1. Write and modify authored/ source.
    2. +
    3. Run builds and place artifacts in scratchpad/made, then execute the promote write script to copy artifacts to consumer/made for testing.
    4. +
    5. Run experiments in experiment/. These experiments can sometimes be promoted to formal tests, but there is no requirement to do so. The developer role should not blur into the tester role; experiments are informal, whereas tests are formal and retained.
    6. +
    7. Strict Boundary: A developer never writes into the tester/ directory. Instead, a developer adds tests to developer/experiment/ and offers to share them.
    8. +
    + +

    Tester role

    +

    Responsibilities and Boundaries:

    +
      +
    1. Evaluate candidates under consumer/made/ and run regression suites to confirm: +
        +
      • That the code does not crash.
      • +
      • That consumers do not have a bad experience.
      • +
      • That the goals specified by the product manager are met.
      • +
      +
    2. +
    3. File issues and communicate feedback to the developers.
    4. +
    5. Strict Boundary: A tester never patches code in the developer/ directory. Instead, the tester files issues or proposes code fixes on a separate branch.
    6. +
    + +

    Consumer role

    +

    Responsibilities:

    +
      +
    1. Act as the end-user simulation environment.
    2. +
    3. Consume and deploy artifacts exclusively from the consumer/made/ target.
    4. +
    5. Never author or modify code; strictly run local builds or deployments for architecture-specific testing.
    6. +
    7. Report issues. (Anyone can report an issue, and consumers regularly do).
    8. +
    + +

    External roles

    +

    + These roles drive the project forward but do not have a dedicated setup <role> workspace, as they do not directly build or test the code in that capacity. If these individuals need to interface with the code, they simply put on a workspace role hat, such as administer, developer, tester, or consumer. +

    + +

    Product manager

    +

    + The product manager receives specifications from the architect and sets the specific goals for a release. When the project manager provides updates indicating readiness, the product manager makes the final decision to cut a project release. +

    + +

    Project manager

    +

    + The project manager owns the schedule (such as the Gantt chart) and monitors progress. They coordinate code reviews, read issues and announcements, reformulate the schedule, and instruct the product manager. When parts of the codebase are outsourced, the project manager serves as the primary point of contact with the external teams. +

    + +

    Architect

    +

    + The architect writes the original specification, sets the technical direction, and performs code reviews. The architect might be a contractor or an in-house team member. +

    + +

    The four interacting loops

    +

    + The project moves forward through the continuous interaction of four distinct operational cycles. +

    + +
      +
    1. Developer Loop: Write code, compile to the scratchpad made directory, promote to the consumer made directory, announce the promotion to the tester, read issues, and repeat.
    2. +
    3. Tester Loop: Read developer announcements, read the consumer made directory, run tests, and file issues.
    4. +
    5. Product Manager Loop: Provide specifications to the developer, receive progress updates from the project manager, and instruct the project administrator to make new git branch project releases and deploy.
    6. +
    7. Project Manager Loop: Own the Gantt chart, coordinate code reviews, read issues and announcements, reformulate the Gantt chart, and instruct the product manager.
    8. +
    + +

    Promotion mechanics

    +

    + Building and promotion are separate activities. The developer compiles and places files in developer/scratchpad/made. The developer then runs promote write to transfer those files to consumer/made. +

    +

    + The consumer/made directory is strictly an untracked deployment target. No tools are permitted to rebuild during promotion, and no builds are run directly inside the consumer made directory. +

    + +
    + + diff --git a/document/role-and-workflow_product-maintenance.html b/document/role-and-workflow_product-maintenance.html new file mode 100644 index 0000000..e93f12d --- /dev/null +++ b/document/role-and-workflow_product-maintenance.html @@ -0,0 +1,111 @@ + + + + + Product maintenance roles and workflow + + + + + + + + + + + +

    Maintenance philosophy

    +

    + Deployed software must be a usable quality product for the customer, beyond that we favor addressing issues in new releases, and encourage customers who are having issues to upgrade. This approach is purely one of keeping the maintenance problem tractable. +

    + +

    Maintenance structure

    +

    + Teams mentioned in this document: +

    +
      +
    1. tester team
    2. +
    3. core developer team
    4. +
    5. branch maintenance team
    6. +
    7. triage team
    8. +
    + +

    + Team membership is a role a person takes on. Multiple people can share a single role, and one person might take on multiple roles. +

    + +

    + The tester team develops and maintains the regression suite, the reliability suite, and additional tests. +

    + +

    + The core developer team writes the source code and compiles promotion candidates. +

    + +

    + The branch maintenance team is typically a subset of the core developer team. This group is dedicated to applying approved fixes to specific release branches. +

    + +

    + The triage team evaluates defects reported against active releases. The team usually includes a developer contact, the project manager, and the product manager. +

    + +

    Issue queues

    +

    + The project maintains two primary issue queues: +

    +
      +
    1. released product
    2. +
    3. core developer
    4. +
    + +

    + There can be additional queues for multiple branches of development, and for experiments. +

    + +

    Core developer queue

    +

    + This queue serves the core_developer_branch. A tester writes a test for every issue reported in this queue. Doing so isolates the defect, proves the fix, and guards against future regressions. +

    + +

    Released product queue

    +

    + All issues found in release branches go onto this queue initially. It tracks defects reported against deployed software across any major version. +

    + +

    Triage and patching

    +

    + Guided by the project philosophy, the triage team reviews each release queue issue to determine its impact. The team assesses whether the defect affects the core_developer_branch, assuming by default that it probably does. The team also determines if the defect is critical enough to warrant a patch on one or more active release branches. +

    +

    + Based on this assessment, the triage team files actionable tickets in the core developer queue. A ticket explicitly specifies its target branches. A ticket will be filed against either the core developer branch, specific release branches, or a combination of both. +

    +

    + Members of the tester team also file tickets directly into the core developer queue when they discover defects in the core branch. +

    +

    + Responsibility for resolving a ticket depends on its target. The core developer team addresses fixes required on the core_developer_branch. The branch maintenance team addresses fixes required on the release_v<major> branches. +

    +

    + When a release is patched, the branch name remains static. The administrator advances the minor release number in the shared/tool/version file and tags the commit. +

    + +
    + + diff --git a/document/writing_tool-script.html b/document/writing_tool-script.html new file mode 100644 index 0000000..5942d3f --- /dev/null +++ b/document/writing_tool-script.html @@ -0,0 +1,287 @@ + + + + + Writing a tool script + + + + + + + + + + + +

    Purpose

    + +

    + A tool is a script a person runs from a role setup to do work on files. This document gives the conventions for how an RT tool presents itself on the command line, reports what it does, and changes files safely. It complements RT-code-format.html, which covers identifier names, layout, and the separation of the CLI() function from the work functions. Those rules apply here and are not repeated. +

    + +

    + The conventions serve three needs of the person at the keyboard. They must be able to see what a tool will do before it does it, read afterward what it did, and run it again without harm. +

    + +

    + Examples are taken from RT-Style_header_v5_to_v6, the administrator tool that converts RT-Style version 5 document headers to version 6. +

    + +

    The command line

    + +

    No arguments prints the help

    + +

    + A tool given no arguments prints its help message and exits with status 0. A person who types the bare name of a tool is asking what it does. Nothing on the file system changes. +

    + +

    The first argument is a command

    + +

    + The first argument names a command, written as a word, not a flag. The words help, -h and --help all print the help message. Further arguments, when a command accepts any, follow the command. A command that accepts no further arguments rejects them with an error. +

    + +

    + An unknown command prints an error naming it, then the help message, and exits with a nonzero status. +

    + +

    The help message is the module docstring

    + +

    + In Python the help message is the module docstring, printed by print(__doc__.strip()). The documentation at the top of the file and the message the person sees are then the same text, and cannot drift apart. The docstring opens with the tool name, a paragraph on what the tool does, then the usage and one line per command: +

    + + + Usage: RT-Style_header_v5_to_v6 <command> [file ...] + RT-Style_header_v5_to_v6 preview <command> [file ...] + + help | -h | --help print this message (also printed when given no arguments) + project walk the directory tree rooted at $REPO_HOME + tree walk the directory tree rooted at the current directory + local only the current directory, no subdirectories + file <name> ... only the named .html files + preview <command> print the log of what <command> would do, change nothing + + +

    Scope commands

    + +

    + A tool that acts on files found in a directory tree offers the same four commands for choosing which files: +

    +
      +
    • project walks the tree rooted at $REPO_HOME. When REPO_HOME is not set, the command is an error. Otherwise it behaves as tree run from $REPO_HOME, from whatever directory the person is in.
    • +
    • tree walks the tree rooted at the current directory.
    • +
    • local acts on the current directory only, without descending.
    • +
    • file acts on the files named after it.
    • +
    + +

    + Only project depends on a Harmony project. The others work anywhere, because the files a tool serves are not always in a project. RT-Style documents, for example, appear in projects and outside them. +

    + +

    Preview

    + +

    + A tool that changes files offers preview, placed before any other command. preview tree prints the log that tree would print and changes nothing. The person reads it, decides, then runs the command without preview. +

    + +

    + Preview is not a separate code path. The work functions are given a preview flag and test it only at the point of each action, immediately before writing or deleting. Everything else, finding files, deciding what to do, and logging, runs the same in both modes. A preview log that came from different code could not be trusted to describe the real run. +

    + +

    The log

    + +

    + A tool that acts on files prints a log of each thing it did. The log is for a person reading a terminal, and for that same person reading it again in a scrollback or a saved file later. This is the log of converting the Harmony skeleton documents, with the path on the first line shortened to fit the page: +

    + + + RT-Style_header_v5_to_v6 project in /home/Thomas/.../project/Harmony + converted administrator/document/how-to_release.html + converted developer/document/naming_file-and-directory.html + converted developer/document/single-file_C-module-and-namespace.html + converted document/introduction_Harmony.html + converted document/role-and-workflow_product-development.html + converted document/role-and-workflow_product-maintenance.html + + summary: + 6 .html files converted to version 6 + 0 RT-Manuscript_locator.js files deleted + 0 .html files already version 6 + 0 .html files with mentions to edit by hand + 0 other .html files left alone + + +
      +
    • The first line names the tool, the command, and the absolute directory the work is rooted in, so a saved log says where it came from.
    • +
    • Each action is one line. A fixed-width tag in the past tense says what happened, followed by the path relative to the root. Relative paths keep lines short and make logs from different machines comparable.
    • +
    • In a preview the tags read would convert, would delete, and the summary ends with preview only: nothing was changed.
    • +
    • A decision not to act is logged too, with the reason in parentheses, as in kept shared/document/RT-Manuscript_locator.js (no version 5 document beside it). A file the tool left alone on purpose must be distinguishable from one it missed.
    • +
    • Work the tool cannot do safely is logged with the tag edit by hand, the file, the line number and the line, as in edit by hand .../Document/user.html:83: &lt;script src=..., so a person can go straight to it.
    • +
    • A summary of counts ends the log. A count line that would always be zero for a run, such as errors, may be left out when it is zero.
    • +
    • Walk directories and files in sorted order, so two runs over the same tree produce the same log.
    • +
    + +

    + Errors are logged on their own lines with the tag error, the tool carries on with the remaining files, and the exit status is nonzero when any error occurred. Errors in the command line itself are printed to standard error before any work starts. +

    + +

    Walking directories

    + +

    + A tool walks every directory in its scope, whatever the directory is named. It does not skip directories by name, since a name that means nothing to the tool's author may hold files that matter to its user. +

    + +

    + A tool does not follow symbolic links to directories. A Harmony project has shared/project-dependency/project pointing to ../../../, the directory holding all the projects. Following it would send the walk into every neighboring project and around the loop back into this one. In Python, os.walk does not follow such links unless given followlinks=True, so leave that argument out. +

    + +

    Paths

    + +

    + A tool forms an absolute path only by starting from an environment variable that the role setup provides, such as $REPO_HOME. Paths the tool writes into files, and symbolic links it creates, are relative. +

    + +

    + An absolute path written into a file is correct for only one view of the file system. Under the subu arrangement, the master user reaches a sub-user's files through a mapped mount at a different absolute path from the one the sub-user sees. A path recorded by one of them is then unreadable, or points somewhere else, for the other. A relative path is resolved from wherever the file is reached, so it is correct in every view. +

    + +

    Changing files safely

    + +

    Checkpoint first

    + +

    + A tool edits files in place and does not make backup copies. The person checkpoints the project with git before running it, and reviews the result with git diff. Run preview first. +

    + +

    Change only what is recognized exactly

    + +

    + Match the precise form the tool was written to change, in the precise place it occurs. RT-Style_header_v5_to_v6 replaces a locator script line only when it stands alone on a line inside <head>. A mention of the locator anywhere else, such as an escaped example in a manual, is not touched. It is logged for a person to edit. +

    + +

    Keep everything not being changed

    + +

    + Carry over what the edit is not about: the indentation of a replaced line, line endings, file permissions, and every other line of the file byte for byte. After a run, git diff should show the intended change and nothing else. +

    + +

    Running twice does no harm

    + +

    + A second run of the same command finds nothing left to do, changes nothing, and says so in its summary. A tool achieves this by recognizing work already done. The header converter counts a file holding the version 6 block as already version 6 and leaves it alone. A person can therefore rerun a tool to confirm a job is complete, or to finish one that stopped part way. +

    + +

    Before deleting, check nothing still uses it

    + +

    + A tool deletes a file only when nothing remaining depends on it. In testing, the header converter run with file on two of six test documents in a directory deleted the shared locator file while the other four still needed it. The rule now is that a locator is deleted only when no unconverted version 5 document beside it still loads it. Otherwise it is kept, and the log names the documents still using it. +

    + +

    Leave alone what the tool cannot identify as its own

    + +

    + A file with the right name in an unexpected place is not evidence that the tool should remove it. The converter keeps a locator file that has no version 5 document beside it, and logs it as kept, leaving the decision to a person. +

    + +

    Testing a tool

    + +

    + Test a new tool on a copy of real material before it runs on the original. An archive of the project, extracted into a scratch directory, gives realistic files that can be discarded. +

    +
      +
    1. Run with no arguments, an unknown command, and each command's error cases, such as project with REPO_HOME unset or file naming a file that does not exist.
    2. +
    3. Run preview and confirm that nothing changed, for example with diff -r against an untouched copy.
    4. +
    5. Run each scope command from more than one directory, including from outside any project.
    6. +
    7. Run each command twice. The second run must report nothing left to do.
    8. +
    9. Run on part of a job, such as file on some of the documents in a directory, and confirm the rest still work.
    10. +
    11. Read the diff of a changed file in full.
    12. +
    + +

    A skeleton

    + +

    + A Python tool laid out to these conventions begins with #!/usr/bin/env python3, then the Emacs mode line the Harmony tools carry, which sets two-space indentation and no tabs. The rest of the skeleton follows: +

    + + + """ + tool_name + + What the tool does, in a paragraph. + + Usage: tool_name <command> [file ...] + tool_name preview <command> [file ...] + + help | -h | --help print this message, also printed given no arguments + project walk the tree rooted at $REPO_HOME + tree walk the tree rooted at the current directory + local only the current directory + file <name> ... only the named files + preview <command> print the log of <command>, change nothing + """ + + import sys ,os + from pathlib import Path + + def work(dirpa_root ,recursive ,preview): + ... + + def print_help(): + print(__doc__.strip()) + return 0 + + def CLI(argv=None): + if argv is None: argv = sys.argv[1:] + if not argv or argv[0] in ("help" ,"-h" ,"--help"): return print_help() + + preview = argv[0] == "preview" + if preview: argv = argv[1:] + if not argv: + print("Error: 'preview' needs a command after it." ,file=sys.stderr) + return 1 + + command ,rest = argv[0] ,argv[1:] + ... + + if __name__ == "__main__": + sys.exit(CLI()) + + +

    Exercises

    + +

    Exercise 1: The bare tool

    +

    + A tool named tidy deletes editor backup files ending in ~. When run with no arguments it deletes them from the current directory. List what is wrong with that behavior under these conventions, and give the command line it should have instead. +

    + +

    Exercise 2: A log line

    +

    + A tool prints Processing /home/Thomas/subu/developer/project/Two/document/a.html ... done for every file it looks at, whether or not it changed it. Rewrite the log for a run in which one of three files was changed, one was already correct, and one could not be read. +

    + +

    Exercise 3: A safe delete

    +

    + A tool replaces each include.css link in an HTML document with an inline style block, then deletes include.css from the directory. Describe the run in which this breaks a document, and the check that prevents it. +

    + +
    + + diff --git a/setup b/setup old mode 100644 new mode 100755 index ebc1244..8ab56c8 --- a/setup +++ b/setup @@ -1,73 +1,67 @@ -#!/usr/bin/env bash +#!/usr/bin/env python3 # setup - enter a project role environment -# (must be sourced) -script_afp=$(realpath "${BASH_SOURCE[0]}") -if [ "${BASH_SOURCE[0]}" == "${0}" ]; then - echo "${script_afp}:: This script must be sourced, not executed." - exit 1 -fi +import os +import sys +import argparse +import tempfile -project_roles="administrator consumer developer tester" +PROJECT_ROLES = ["administrator", "consumer", "developer", "tester"] -print_usage(){ - echo "usage: . setup " - echo "known roles: ${project_roles}" -} +def main(): + parser = argparse.ArgumentParser( + description="Enter a project role environment by spawning an isolated subshell." + ) + parser.add_argument("role", choices=PROJECT_ROLES, help="The project role to assume.") + args = parser.parse_args() -if [ -z "${1:-}" ] || [ "${1}" == "-h" ] || [ "${1}" == "--help" ]; then - print_usage - return 0 -fi + env = os.environ.copy() -role_is_valid=false -for r in ${project_roles}; do - if [ "${1}" == "${r}" ]; then - role_is_valid=true - break - fi -done + # Python-domain logic: Variables and Paths + repo_home = env.get("REPO_HOME", os.getcwd()) + role = args.role + role_home = os.path.join(repo_home, role) + tool_dir = os.path.join(role_home, "tool") + setup_file = os.path.join(role, "tool", "setup") -if [ "${role_is_valid}" == "false" ]; then - echo "setup: unrecognized role or option '${1}'" - print_usage - return 1 -fi + env["ROLE"] = role + env["ROLE_HOME"] = role_home + env["SETUP"] = setup_file -# setup the project -# - source shared/tool/setup - if [[ -f "shared/authored/setup" ]]; then - source shared/authored/setup - fi + print(f"ROLE_HOME {role_home}") -# setup tools -# - export PYTHON_HOME="${REPO_HOME}/shared/linked-project/Python" - if [[ ":${PATH}:" != *":${PYTHON_HOME}/bin:"* ]]; then - export PATH="${PYTHON_HOME}/bin:${PATH}" - fi + # Python-domain logic: Directory validation + if not os.path.isdir(role): + print(f"setup: failed to locate directory for role '{role}'") + sys.exit(1) - RT_gcc="${REPO_HOME}/shared/linked-project/RT_gcc/release" - if [[ ":${PATH}:" != *":${RT_gcc}:"* ]]; then - export PATH="${RT_gcc}:${PATH}" - fi + # Bash-domain logic: The rcfile only handles operations Python cannot inherit + # (sourcing bash functions) and performs the directory change internally to + # guarantee the shared scripts are sourced from the project root. + bash_logic = [ + 'if [[ -f ~/.bashrc ]]; then source ~/.bashrc; fi', + 'if [[ -f "shared/tool/setup" ]]; then source "shared/tool/setup"; fi', + 'if [[ -f "shared/authored/setup" ]]; then source "shared/authored/setup"; fi', + '# the role tool directory is searched before everything the shared setup added', + f'PATH="{tool_dir}:$PATH"; export PATH', + f'cd "{role}"', + 'if [[ -f "tool/setup" ]]; then', + ' source "tool/setup"', + f' echo "in environment: {setup_file}"', + 'else', + f' echo "not found: {setup_file}"', + 'fi', + 'if [[ -n "${INSIDE_EMACS}" ]] && command -v env_to_emacs >/dev/null 2>&1; then', + ' env_to_emacs', + 'fi', + 'rm -f "${BASH_SOURCE[0]}"' + ] -# setup the role -# - export ROLE="${1}" + fd, rcfile_path = tempfile.mkstemp(suffix="-rt-setup.sh") + with os.fdopen(fd, "w") as f: + f.write("\n".join(bash_logic) + "\n") - tool="${REPO_HOME}/${ROLE}/tool" - if [[ ":${PATH}:" != *":${tool}:"* ]]; then - export PATH="${tool}:${PATH}" - fi + os.execvpe("bash", ["bash", "--rcfile", rcfile_path], env) - export SETUP="${ROLE}/tool/setup" - - cd "${ROLE}" || return 1 - if [ -f "tool/setup" ]; then - source "tool/setup" - echo "in environment: ${SETUP}" - else - echo "not found: ${SETUP}" - fi +if __name__ == "__main__": + main() diff --git a/shared/project-dependency/.gitignore b/shared/project-dependency/.gitignore new file mode 100644 index 0000000..a033c31 --- /dev/null +++ b/shared/project-dependency/.gitignore @@ -0,0 +1,11 @@ +# shared/project-dependency/.gitignore +# Tracks the skeleton's parts. Add a ! line for anything else to track, +# such as a dependency's link. +* +!.gitignore +!project +!*.init +!*.install +!*.org +!*.html +!Python-3.12.3 diff --git a/shared/project-dependency/Python-3.12.3 b/shared/project-dependency/Python-3.12.3 new file mode 120000 index 0000000..c2895cf --- /dev/null +++ b/shared/project-dependency/Python-3.12.3 @@ -0,0 +1 @@ +project/Python-3.12.3 \ No newline at end of file diff --git a/shared/project-dependency/Python.init b/shared/project-dependency/Python.init new file mode 100644 index 0000000..eddbaad --- /dev/null +++ b/shared/project-dependency/Python.init @@ -0,0 +1,16 @@ +# -*- mode: sh; sh-shell: bash; -*- +# init files are bash script + +# Python.init +# -------------------------------------------------------------------------------- +# Ensure the local Python build is prioritized in the PATH. +# The user must establish the Python-3.12.3 symlink for this to activate. + +if [[ -d "$REPO_HOME/shared/project-dependency/Python-3.12.3/bin" ]]; then + PATH="$REPO_HOME/shared/project-dependency/Python-3.12.3/bin:$PATH" +else + # Optional: Issue a quiet warning for the administrator role + if [[ "$ROLE" == "administrator" ]]; then + echo "Notice: Local Python-3.12.3 bin not found. Using system Python." + fi +fi diff --git a/shared/project-dependency/Python.org b/shared/project-dependency/Python.org new file mode 100644 index 0000000..a5ba50c --- /dev/null +++ b/shared/project-dependency/Python.org @@ -0,0 +1,38 @@ +#+TITLE: Python Installation Guide +#+AUTHOR: Project Administrator + +This project requires a dedicated Python environment. The configuration plugin (`shared/project-dependency/Python.init`) was written targeting Python 3.12.3, though the architecture does not rely on any known version-dependent features. + +* 1. Download and Extract +Download the Python source code and extract it as a sibling to the main project directory. Assuming you are at the top level of your project workspace: + +#+begin_src bash +cd ../ +wget https://www.python.org/ftp/python/3.12.3/Python-3.12.3.tgz +tar -xzf Python-3.12.3.tgz +cd Python-3.12.3 +#+end_src + +* 2. Build the Environment +Compile the Python binaries in place. Setting the installation prefix to the current directory isolates the build, and enabling shared libraries ensures compatibility with C extensions. + +#+begin_src bash +./configure --prefix="$(pwd)" --enable-optimizations --enable-shared +make -j8 +make install +#+end_src + +* 3. Link the Project + +Python-3.12.3 is linked to a Harmony derived project by default. + +See administrator/document/how-to_add-project-dependency.html for details about linking a project, if you want to check the setup. + +If you are installing a different version, then at least the Python-3.12.3 version project linking is there as a good example. + +Project linking is not difficult, make a sym link to `project/`, and then add an init file, as described in administrator/document/how-to_add-project-dependency.html. The 'project' in the link is literal. + + + + + diff --git a/shared/project-dependency/project b/shared/project-dependency/project new file mode 120000 index 0000000..1b20c9f --- /dev/null +++ b/shared/project-dependency/project @@ -0,0 +1 @@ +../../../ \ No newline at end of file diff --git a/tester/deprecated/.githolder b/shared/tool/PATH.sh similarity index 100% rename from tester/deprecated/.githolder rename to shared/tool/PATH.sh diff --git a/shared/tool/env_to_emacs b/shared/tool/env_to_emacs new file mode 100755 index 0000000..947efc0 --- /dev/null +++ b/shared/tool/env_to_emacs @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +script_afp=$(realpath "${BASH_SOURCE[0]}") + +set -euo pipefail + +if [ -z "${REPO_HOME:-}" ]; then + echo "emacs-sync:: error: REPO_HOME is not set. Source a setup environment first." + exit 1 +fi + +if ! command -v emacsclient >/dev/null 2>&1; then + echo "emacs-sync:: error: emacsclient is not installed or not in PATH." + exit 1 +fi + +# Push variables to the Emacs global environment +emacsclient --eval "(setenv \"REPO_HOME\" \"$REPO_HOME\")" > /dev/null +emacsclient --eval "(setenv \"PROJECT\" \"$PROJECT\")" > /dev/null + +if [ -n "${ROLE:-}" ]; then + emacsclient --eval "(setenv \"ROLE\" \"$ROLE\")" > /dev/null +fi + +# Sync the PATH so Emacs subprocesses find the local tools +emacsclient --eval "(setenv \"PATH\" \"$PATH\")" > /dev/null + +# Update exec-path for native Emacs executable resolution +emacsclient --eval "(setq exec-path (append (parse-colon-path \"$PATH\") (list exec-directory)))" > /dev/null + +echo "Emacs environment synchronized for project: $PROJECT" diff --git a/shared/tool/setup b/shared/tool/setup index 58af6d9..a7a66aa 100644 --- a/shared/tool/setup +++ b/shared/tool/setup @@ -14,15 +14,13 @@ umask 0077 # -------------------------------------------------------------------------------- # project definition -# actual absolute director path for this script file - +# actual absolute directory path for this script file script_adp(){ dirname "$script_afp" } -# assume this script is located $REPO_HOME/tools_shared/authored and work backwards -# to get $REPO_HOME, etc. - +# This script is located at $REPO_HOME/shared/tool/setup. +# Ascend two directories to discover the absolute $REPO_HOME path. REPO_HOME=$(dirname "$(dirname "$(script_adp)")") echo REPO_HOME "$REPO_HOME" @@ -34,30 +32,6 @@ umask 0077 export REPO_HOME PROJECT PROMPT_DECOR -# -------------------------------------------------------------------------------- -# Project wide Tool setup -# - -export VIRTUAL_ENV="$REPO_HOME/shared/linked-project/Python" -export PYTHON_HOME="$VIRTUAL_ENV" -unset PYTHONHOME - - -# -------------------------------------------------------------------------------- -# PATH -# precedence: last defined, first discovered - - PATH="$REPO_HOME/shared/authored:$PATH" - PATH="$REPO_HOME/shared/made:$PATH" - PATH="$REPO_HOME/shared/tool:$PATH" - - # Remove duplicates - clean_path() { - PATH=$(echo ":$PATH" | awk -v RS=: -v ORS=: '!seen[$0]++' | sed 's/^://; s/:$//') - } - clean_path - export PATH - # -------------------------------------------------------------------------------- # the following functions are provided for other scripts to use. # at the top of files that make use of these functions put the following line: @@ -69,7 +43,7 @@ unset PYTHONHOME basename "$script_afp" } - ## script's dirpath relative to $REPO_HOME + ## script's file path relative to $REPO_HOME script_fp(){ realpath --relative-to="${REPO_HOME}" "$script_afp" } @@ -81,43 +55,31 @@ unset PYTHONHOME export -f script_adp script_fn script_dp script_fp -#-------------------------------------------------------------------------------- -# used by release scripts -# - - install_file() { - if [ "$#" -lt 3 ]; then - echo "env::install_file usage: install_file ... " - return 1 +# -------------------------------------------------------------------------------- +# PATH & Plugin Sourcing +# Each addition goes to the front, so the last added is searched first. +# Resulting search order, most specific first: +# shared/authored, shared/made, shared/tool, project dependencies, the rest. +# The top-level setup then puts the role's tool directory in front of these. + + # Project dependencies: source each .init file. Each prepends its own entries. + for init_file in "$REPO_HOME/shared/project-dependency/"*.init; do + if [[ -f "$init_file" ]]; then + source "$init_file" fi + done - perms="${@: -1}" # Last argument is permissions - target_dp="${@: -2:1}" # Second-to-last argument is the target directory - sources=("${@:1:$#-2}") # All other arguments are source files - - if [ ! -d "$target_dp" ]; then - echo "env::install_file no install done: target directory '$target_dp' does not exist." - return 1 - fi + PATH="$REPO_HOME/shared/tool:$PATH" + PATH="$REPO_HOME/shared/made:$PATH" + PATH="$REPO_HOME/shared/authored:$PATH" - for source_fp in "${sources[@]}"; do - if [ ! -f "$source_fp" ]; then - echo "env::install_file: source file '$source_fp' does not exist." - return 1 - fi - - target_file="$target_dp/$(basename "$source_fp")" - - if ! install -m "$perms" "$source_fp" "$target_file"; then - echo "env::install_file: Failed to install $(basename "$source_fp") to $target_dp" - return 1 - else - echo "env::install_file: installed $(basename "$source_fp") to $target_dp with permissions $perms" - fi - done + # Remove duplicates, keeping the first occurrence + clean_path() { + PATH=$(echo ":$PATH" | awk -v RS=: -v ORS=: '!seen[$0]++' | sed 's/^://; s/:$//') } + clean_path - export -f install_file + export PATH # -------------------------------------------------------------------------------- # closing @@ -125,4 +87,3 @@ unset PYTHONHOME if [[ -z "$ENV" ]]; then export ENV=$(script_fp) fi - diff --git a/shared/tool/version b/shared/tool/version index 1fe2129..b264028 100755 --- a/shared/tool/version +++ b/shared/tool/version @@ -1,5 +1,2 @@ -echo "Harmony v3.0 2026-05-10" -echo "SubU v3.0 2025-05-19" - - - +echo "Harmony v3.5 2026-10-14 04:51:11 Z" +echo "SubU v4.0 2026-10-14 04:51:11 Z" diff --git a/tester/document/.githolder b/tester/.gitkeep similarity index 100% rename from tester/document/.githolder rename to tester/.gitkeep diff --git a/tester/shell/.githolder b/tester/authored/.gitkeep similarity index 100% rename from tester/shell/.githolder rename to tester/authored/.gitkeep diff --git a/tester/tool/setup b/tester/tool/setup new file mode 100644 index 0000000..0b993ad --- /dev/null +++ b/tester/tool/setup @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +script_afp=$(realpath "${BASH_SOURCE[0]}") + diff --git a/tool/.githolder b/tool/.githolder deleted file mode 100644 index e69de29..0000000 -- 2.20.1